用C++的odeint库求解微分方程
介绍
求解微分方程的数值解一般使用MATLAB等数值计算软件,其实C++也可以求解微分方程,要用到odeint库,它是boost库的一部分。官方教程和示例比较晦涩,本文力求用较短的篇幅介绍它的基本用法。
所求解的微分方程的标准形式为 \[ \dot{\mathbf{x}} = \mathbf{f}(\mathbf{x}, t),\, \mathbf{x}(0) = \mathbf{x_0} \]
这是一阶微分方程组,\(\mathbf{x}\)和\(\mathbf{f}(\mathbf{x}, t)\)均为向量。高阶微分方程要先转换成一阶微分方程组,然后再用odeint求解。
integrate函数
API中最重要的是integrate函数,一共有5种,它们的调用接口很类似。integrate_const
的函数调用方式为:
1 | integrate_const(stepper, system, x0, t0, t1, dt, observer) |
其中:
stepper
是求解器,也就是所使用的数值算法(例如Runge-Kutta法、Euler法)system
是待求解的微分方程x0
是初始条件t0
和t1
分别是初始时间和结束时间dt
是时间间隔,它重要与否取决于求解器的类型observer
是每N个时间间隔调用一次的函数,可用来打印实时的解,该参数是可选的,如果没有此参数,integrate函数会从t0
计算到t1
,不产生任何输出就返回
给定初始状态x0
,integrate函数从初始时间t0
到结束时间t1
不断地调用给定的stepper
,计算微分方程在不同时刻的解,用户还可以提供observer
以分析某个时刻的状态值。具体选择哪个integrate函数取决于你想要什么类型的结果,也就是调用observer
的频率。
integrate_const
每过相等的时间间隔dt
会调用一次observer
,语法为:
1 | integrate_const(stepper, system, x0, t0, t1, dt, observer) |
integrate_n_steps
和前面的类似,但它不需要知道结束时间,它只需要知道要计算的步数,语法为:
1 | integrate_n_steps(stepper, system, x0, t0, dt, n, observer) |
integrate_times
计算用户给定时间点上的值,语法为:
1 | integrate_times(stepper, system, x0, times_start, times_end, dt, observer) |
integrate_adaptive
用于需要在每个时间间隔都调用observer
的场合,语法为:
1 | integrate_adaptive(stepper, system, x0, t0, t1, dt, observer) |
integrate
最方便,
不需要指定stepper
,简单快捷,语法为:
1 | integrate(system, x0, t0, t1, dt, observer) |
求解器stepper的选择(比如自适应方式会根据误差修改时间间隔)会改变计算的具体实现方式, 但是observer的调用(也就是输出结果)依然遵循上述规则。
求解单摆模型
微分方程标准化
现在求单摆系统微分方程的解,以得到单摆角度随时间变化的规律。其微分方程 \[ \ddot{\theta}(t) = -\mu \dot{\theta}(t) - \frac{g}{L} \sin \theta(t) \]
\(\theta(t)\)是摆角随时间变化的规律,\(\mu\)是空气阻力系数,\(g\)是重力加速度,\(L\)是摆球到悬挂点的长度。首先将它转化成一阶微分方程组,新增一个变量角速度\(\omega(t) = \dot{\theta}(t)\),方程变为 \[ \begin{align*} \dot{\theta}(t) & = \omega(t) \\ \dot{\omega}(t) & = -\mu \omega(t) - g \sin \theta(t) / L \end{align*} \]
令状态变量 \[ \mathbf{x} = \begin{bmatrix} x_1(t)\\ x_2(t) \end{bmatrix} = \begin{bmatrix} \theta(t)\\ \omega(t) \end{bmatrix} \]
微分方程组变为 \[ \dot{\mathbf{x}}= \begin{bmatrix} \dot{x}_1(t)\\ \dot{x}_2(t) \end{bmatrix} =\mathbf{f}(\mathbf{x}, t)= \begin{bmatrix} x_2(t)\\ -\mu x_2(t) - g \sin x_1(t) / L \end{bmatrix} \]
代码实现
代码中有如下几个关键点:
- 要定义状态变量的类型,将
state_type
简单定义为std::vector<double>
即可 - 要用方程表示微分方程模型,和MATLAB中模型方程的写法非常类似
- 要写一个Observer以打印出计算结果,Observer函数也可以直接将数据写入文件中
- 要选择合适的求解器stepper,各种stepper的特点总结可以看这里
- 要根据需要选择合适的integrate函数,一般选择
integrate_const
即可满足要求
下面的代码可作为标准模板使用:
1 |
|
编译该程序依赖boost库,要在CMakeLists.txt
中添加相应的内容。编译成功后运行,会得到如下的结果:
1 | 0 0.1 0 |
可以将输出数据重定向到文本文件data.txt
中,然后使用Python等脚本语言提取数据并画图显示。下面是实现该功能的参考代码:
1 | import numpy as np |