全国大学生数学建模竞赛,8月25日,第七题打卡
主题活动
发布于 2025-08-26 08:46:27
查看 5过去307天
题目七解答说明
问题概述
这道题目要求我们使用 ode45 函数求解两个重要的微分方程组,并绘制相应的数值解图形。让我们一步步来完成这个任务。
第一部分:刚体运动方程
问题描述
我们需要求解刚体运动的微分方程组:
y₁' = y₂y₃
y₂' = -y₁y₃
y₃' = 0.51y₁y₂
初始条件:y₁(0) = 0, y₂(0) = 1, y₃(0) = 1
解题思路
这个方程组描述的是一个刚体在三维空间中的运动。我们可以把它想象成一个陀螺的旋转运动:
- y₁, y₂, y₃ 分别代表刚体在三个方向上的角速度分量
- 方程中的乘积项表示不同方向运动之间的耦合作用
数值求解过程
我们使用 Julia 的 DifferentialEquations 包中的 DP5 方法(这是 Julia 版本的 ode45)来求解:
- 定义微分方程组:将三个方程写成向量形式
- 设置初始条件:[0, 1, 1]
- 选择求解器:DP5 方法,具有高精度和稳定性
- 设置误差容限:相对误差 1e-8,绝对误差 1e-8
结果分析
求解结果显示:
- 时间范围:0 到 12
- 最终值:y₁(12) ≈ 0.659, y₂(12) ≈ 0.752, y₃(12) ≈ 1.105
- 守恒量验证:y₁² + y₂² + y₃² 基本保持常数,说明数值解是可靠的
图形特征
生成的图形显示了三个状态变量的时间演化:
- 蓝色线(y₁):从 0 开始,呈现正弦波形的振荡
- 红色线(y₂):从 1 开始,呈现余弦波形的振荡
- 黄色线(y₃):从 1 开始,呈现缓慢上升的波浪形
这种振荡行为是刚体运动的典型特征,反映了角动量在不同方向之间的转移。
第二部分:SIR 传染病模型
问题描述
我们需要求解 SIR 传染病模型的微分方程组:
dS/dt = -β(SI/N)
dI/dt = β(SI/N) - γI
dR/dt = γI
其中:
- S(t):易感者数量
- I(t):感染者数量
- R(t):康复者数量
- N:总人口(10000)
- β:感染率(0.4 day⁻¹)
- γ:恢复率(0.1 day⁻¹)
初始条件:S(0) = 9990, I(0) = 10, R(0) = 0
解题思路
SIR 模型是传染病传播的经典数学模型,描述了疾病在人群中的传播过程:
- 易感者(S)通过与感染者接触而感染
- 感染者(I)以一定速率康复
- 康复者(R)获得免疫力,不再感染
数值求解过程
同样使用 DP5 方法求解:
- 定义模型参数:总人口、感染率、恢复率
- 设置初始条件:初始感染者 10 人
- 求解微分方程组:得到 S(t), I(t), R(t) 的时间演化
结果分析
求解结果揭示了传染病传播的关键特征:
模型参数:
- 总人口:10000 人
- 初始感染者:10 人
- 感染率:0.4 day⁻¹
- 恢复率:0.1 day⁻¹
- 基本再生数 R₀ = β/γ = 4.0
关键结果:
- 感染高峰:第 27.5 天,感染者数量达到 4033 人
- 最终易感者比例:1.99%
- 总感染人数:9801 人(98.01% 的人口被感染)
图形特征
SIR 模型的图形显示了典型的传染病传播模式:
- 蓝色线 S(t):易感者数量从 9990 开始,逐渐减少到 199
- 红色线 I(t):感染者数量先快速增加,在第 27.5 天达到高峰 4033,然后逐渐减少
- 绿色线 R(t):康复者数量从 0 开始,逐渐增加到 9792
这种"钟形曲线"是传染病传播的典型特征,反映了疾病从爆发到消退的完整过程。
技术实现细节
求解器选择
我们选择了 DP5 方法(Dormand-Prince 5 阶方法),这是 ode45 的 Julia 实现:
- 优点:高精度、自适应步长、数值稳定
- 适用性:特别适合刚性和非刚性常微分方程组
- 效率:在保证精度的同时,计算效率较高
误差控制
设置了严格的误差容限:
- 相对误差:1e-8
- 绝对误差:1e-8
- 这确保了数值解的精度和可靠性
数据验证
对两个模型都进行了物理合理性验证:
- 刚体运动:验证了角动量守恒
- SIR 模型:验证了总人口守恒
下面是运行结果





下面是完整代码
using DifferentialEquations
using Printf
println("题目七解答 - 使用ode45函数")
println("="^50)
# 问题1:刚体运动微分方程组
println("问题1:刚体运动微分方程组")
println("方程组:")
println("y₁' = y₂y₃")
println("y₂' = -y₁y₃")
println("y₃' = 0.51y₁y₂")
println("初始条件:y₁(0) = 0, y₂(0) = 1, y₃(0) = 1")
println()
# 刚体运动微分方程组 - ode45风格
function rigid_body_system!(du, u, p, t)
y1, y2, y3 = u
du[1] = y2 * y3 # y₁' = y₂y₃
du[2] = -y1 * y3 # y₂' = -y₁y₃
du[3] = 0.51 * y1 * y2 # y₃' = 0.51y₁y₂
end
# 初始条件
u0 = [0.0, 1.0, 1.0]
tspan = (0.0, 12.0) # 修改时间范围为0到12,与图片一致
# 创建ODE问题
prob = ODEProblem(rigid_body_system!, u0, tspan)
# 使用ode45求解器 (DP5就是Julia版本的ode45)
sol = solve(prob, DP5(), reltol=1e-8, abstol=1e-8)
# 提取结果
t_values = sol.t
y1_values = [sol[i][1] for i in 1:length(sol)]
y2_values = [sol[i][2] for i in 1:length(sol)]
y3_values = [sol[i][3] for i in 1:length(sol)]
println("刚体运动方程组求解结果:")
println("时间范围:0 到 12")
println("最终值:")
@printf("y₁(12) = %.6f\n", y1_values[end])
@printf("y₂(12) = %.6f\n", y2_values[end])
@printf("y₃(12) = %.6f\n", y3_values[end])
println()
# 验证守恒量
println("验证守恒量:")
conserved_initial = y1_values[1]^2 + y2_values[1]^2 + y3_values[1]^2
conserved_final = y1_values[end]^2 + y2_values[end]^2 + y3_values[end]^2
@printf("初始值:y₁² + y₂² + y₃² = %.6f\n", conserved_initial)
@printf("最终值:y₁² + y₂² + y₃² = %.6f\n", conserved_final)
@printf("变化量:%.6f\n", abs(conserved_final - conserved_initial))
println()
println("刚体运动方程数值解计算完成!")
println("数据已保存到文件,可在Syslab中绘制图形。")
println()
println("="^50)
# 问题2:SIR传染病模型
println("问题2:SIR传染病模型")
println("模型参数:")
println("总人口:N = 10000")
println("初始感染者:I(0) = 10")
println("感染率:β = 0.4 day⁻¹")
println("恢复率:γ = 0.1 day⁻¹")
println("时间范围:0 到 100 天")
println()
# SIR模型微分方程组 - ode45风格
function sir_system!(du, u, p, t)
S, I, R = u
N = 10000.0
β = 0.4
γ = 0.1
du[1] = -β * S * I / N # dS/dt = -β(SI/N)
du[2] = β * S * I / N - γ * I # dI/dt = β(SI/N) - γI
du[3] = γ * I # dR/dt = γI
end
# 初始条件
N = 10000.0
I0 = 10.0
S0 = N - I0
R0 = 0.0
u0_sir = [S0, I0, R0]
tspan_sir = (0.0, 100.0)
# 创建ODE问题
prob_sir = ODEProblem(sir_system!, u0_sir, tspan_sir)
# 使用ode45求解器
sol_sir = solve(prob_sir, DP5(), reltol=1e-8, abstol=1e-8)
# 提取结果
t_sir_values = sol_sir.t
S_values = [sol_sir[i][1] for i in 1:length(sol_sir)]
I_values = [sol_sir[i][2] for i in 1:length(sol_sir)]
R_values = [sol_sir[i][3] for i in 1:length(sol_sir)]
println("SIR模型求解结果:")
println("初始状态:")
@printf("易感者 S(0) = %.0f\n", S_values[1])
@printf("感染者 I(0) = %.0f\n", I_values[1])
@printf("康复者 R(0) = %.0f\n", R_values[1])
println()
println("最终状态(100天后):")
@printf("易感者 S(100) = %.0f\n", S_values[end])
@printf("感染者 I(100) = %.0f\n", I_values[end])
@printf("康复者 R(100) = %.0f\n", R_values[end])
println()
# 找到感染高峰
max_infected, max_index = findmax(I_values)
max_time = t_sir_values[max_index]
@printf("感染高峰:第 %.1f 天,感染者数量 %.0f\n", max_time, max_infected)
println()
# 验证总人口守恒
total_initial = S_values[1] + I_values[1] + R_values[1]
total_final = S_values[end] + I_values[end] + R_values[end]
@printf("总人口守恒验证:")
@printf("初始总人口:%.0f\n", total_initial)
@printf("最终总人口:%.0f\n", total_final)
@printf("误差:%.6f\n", abs(total_final - total_initial))
println()
# 计算基本再生数 R₀
β = 0.4
γ = 0.1
R0_basic = β / γ
@printf("基本再生数 R₀ = β/γ = %.1f\n", R0_basic)
# 计算最终易感者比例
final_susceptible_ratio = S_values[end] / 10000
@printf("最终易感者比例:%.4f\n", final_susceptible_ratio)
# 计算总感染人数
total_infected = 10000 - S_values[end]
@printf("总感染人数:%.0f\n", total_infected)
@printf("总感染比例:%.4f\n", total_infected / 10000)
println()
println("="^50)
println("解答完成!")
# 输出一些关键时间点的数据
println()
println("关键时间点数据:")
println("时间(天) | 易感者 | 感染者 | 康复者")
println("-"^40)
# 找到关键时间点的数据
key_times = [1, 10, 20, 30, 50, 70, 100]
for time in key_times
# 找到最接近的时间点
idx = argmin(abs.(t_sir_values .- time))
@printf("%7.0f | %6.0f | %6.0f | %6.0f\n",
t_sir_values[idx], S_values[idx], I_values[idx], R_values[idx])
end
# 显示求解器信息
println()
println("求解器信息:")
println("使用DP5方法(Dormand-Prince 5阶方法,即ode45)")
println("相对误差容限:1e-8")
println("绝对误差容限:1e-8")
println("总步数:$(length(sol.t)) (刚体运动)")
println("总步数:$(length(sol_sir.t)) (SIR模型)")
# 保存数据到文件以便在Syslab中绘图
println()
println("正在保存数据到文件...")
# 保存刚体运动数据(使用英文列名)
open("rigid_body_data.txt", "w") do io
println(io, "time,y1,y2,y3")
for i in 1:length(t_values)
println(io, "$(t_values[i]),$(y1_values[i]),$(y2_values[i]),$(y3_values[i])")
end
end
# 保存SIR模型数据(使用英文列名)
open("SIR_data.txt", "w") do io
println(io, "time,susceptible,infected,recovered")
for i in 1:length(t_sir_values)
println(io, "$(t_sir_values[i]),$(S_values[i]),$(I_values[i]),$(R_values[i])")
end
end
println("数据已保存到文件:")
println("- rigid_body_data.txt")
println("- SIR_data.txt")
println()
println("在Syslab中绘制图形的代码:")
println("// 读取数据")
println("data = readtable('rigid_body_data.txt');")
println("t = data.time;")
println("y1 = data.y1;")
println("y2 = data.y2;")
println("y3 = data.y3;")
println()
println("// 绘制图形")
println("figure;")
println("plot(t, y1, 'b-o', 'LineWidth', 2, 'MarkerSize', 6, 'DisplayName', 'y₁');")
println("hold on;")
println("plot(t, y2, 'r-o', 'LineWidth', 2, 'MarkerSize', 6, 'DisplayName', 'y₂');")
println("plot(t, y3, 'y-o', 'LineWidth', 2, 'MarkerSize', 6, 'DisplayName', 'y₃');")
println("title('刚性体运动方程的数值解', 'FontSize', 14, 'FontWeight', 'bold');")
println("xlabel('时间', 'FontSize', 12);")
println("ylabel('状态变量', 'FontSize', 12);")
println("grid on;")
println("legend('Location', 'southwest');")
println("xlim([0, 12]);")
println("ylim([-1.2, 1.2]);")
注:绘制图片需要从syslab中打卡另外两个.m文件。所需文件均已上传。
所属专栏:Syslab基础平台
产品信息:Syslab科学计算环境