用C++的odeint库求解微分方程

目录
  • 1、集成方程
  • 2、求解单摆模型
    • 2.1 微分方程标准化
    • 2.2 代码实现

微分方程的标准形式为:

即:\dot{\boldsymbol{x}} = \boldsymbol{f}(\boldsymbol{x}, t),\, \boldsymbol{x}(0) = \boldsymbol{x_0}

这是一阶微分方程组, \boldsymbol{x} \boldsymbol{f}(\boldsymbol{x}, t) 均为向量。如果要求解高阶微分方程,需要先转换成一阶微分方程组后再用odeint求解。

1、集成方程

API中最重要的是集成函数(integrate functions),一共有5种,它们的调用接口很类似。 integrate_const 的函数调用方式为:

integrate_const(stepper, system, x0, t0, t1, dt, observer)

其中:

  • stepper 是求解器,也就是所使用的数值算法(例如Runge-Kutta或Euler法)
  • system 是待求解的微分方程
  • x0 是初始条件
  • t0 和 t1 分别是初始时间和结束时间
  • dt 是时间间隔,它重要与否取决于求解器的类型
  • observer 是每N个时间间隔调用一次的函数,可用来打印实时的解,该参数是可选的,如果没有此参数,集成函数会从 t0 计算到 t1 ,不产生任何输出就返回

给定初始状态 x0 ,集成函数从初始时间 t0 到结束时间 t1 不断地调用给定的 stepper ,计算微分方程在不同时刻的解,用户还可以提供 observer 以分析某个时刻的状态值。具体选择哪个集成函数取决于你想要什么类型的结果,也就是调用 observer 的频次。

integrate_const 每过相等的时间间隔 dt 会调用一次 observer 语法为:

integrate_const(stepper, system, x0, t0, t1, dt, observer)

integrate_n_steps 和前面的类似,但它不需要知道结束时间,它只需要知道要计算的步数,语法为:

integrate_n_steps(stepper, system, x0, t0, dt, n, observer)

integrate_times 计算在用户给定时间点的值,语法为:

integrate_times(stepper, system, x0, times_start, times_end, dt, observer)
integrate_times(stepper, system, x0, time_range, dt, observer)

integrate_adaptive 用于需要在每个时间间隔调用 observer 的场合,语法为:

integrate_adaptive(stepper, system, x0, t0, t1, dt, observer)

integrate 是最方便的集成函数, 不需要指定 stepper ,简单快捷,语法为:

integrate(system, x0, t0, t1, dt, observer)

求解器stepper的选择(比如自适应方式会根据误差修改时间间隔)会改变计算的具体实现方式, 但是observer的调用(也就是你的输出结果)依然遵循上述规则。

2、求解单摆模型

2.1 微分方程标准化

现在求单摆系统微分方程的解,以得出单摆角度随时间变化的规律。其微分方程

即:\ddot{\theta}(t) = -\mu \dot{\theta}(t) - \frac{g}{L} \sin \theta(t)

即:\begin{cases} \dot{\theta}(t) & = \omega(t) \\ \dot{\omega}(t) & = -\mu \omega(t) - g/L \sin \theta(t) \end{cases}

令状态变量

即:\boldsymbol{x} = \begin{bmatrix} x_1(t)\\ x_2(t) \end{bmatrix} = \begin{bmatrix} \theta(t)\\ \omega(t) \end{bmatrix}

微分方程组变为

即:\frac{\mathrm{d}\boldsymbol{x}}{\mathrm{d}t}= \frac{\mathrm{d}}{\mathrm{d}t} \begin{bmatrix} x_1(t)\\ x_2(t) \end{bmatrix} = \begin{bmatrix} x_2(t)\\ -\mu x_2(t) - g/L \sin x_1(t) \end{bmatrix}

2.2 代码实现

代码中有如下几个关键点:

  1. 要定义状态变量的类型state_type,定义为 std::vector<double> 即可
  2. 要用方程表示微分方程模型,和MATLAB中模型方程的写法非常类似
  3. 要写一个Observer以打印出计算结果,Observer函数也可以直接将数据写入文件中
  4. 要选择合适的求解器stepper,各种stepper的特点总结可以看 这里
  5. 要根据需要选择合适的集成函数,一般选择 integrate_const 即可满足要求

下面的代码可作为标准模板使用:

#include <iostream>
#include <cmath>
#include <boost/numeric/odeint.hpp>

using namespace std;
using namespace boost::numeric::odeint;

const double g  = 9.81; // 重力加速度
const double L  = 1.00; // 摆线长度
const double mu = 0.80; // 阻力系数

// 定义状态变量的类型
typedef std::vector<double> state_type;

// 要求解的微分方程
void pendulum(const state_type &x, state_type &dxdt, double t)
{
    dxdt[0] = x[1];
    dxdt[1] = -mu*x[1] - g/L*sin(x[0]);
}

// Observer打印状态值
void write_pendulum(const state_type &x, const double t)
{
    cout << t << '\t' << x[0] << '\t' << x[1] << endl;
}

int main(int argc, char **argv)
{
    // 初始条件,二维向量
    state_type x = {0.10 , 0.00};
    // 求解方法为runge_kutta4
    integrate_const(runge_kutta4<state_type>(), pendulum, x , 0.0 , 5.0 , 0.01 , write_pendulum);
}

编译该程序依赖boost库,要在 CMakeLists.txt 中添加相应的内容。编译成功后运行,会得到如下的结果:

0       0.1     0
0.01    0.0999512       -0.009753
0.02    0.0998052       -0.0194188
0.03    0.0995631       -0.0289887
0.04    0.0992258       -0.0384542
0.05    0.0987944       -0.0478069
0.06    0.0982701       -0.0570385
0.07    0.0976541       -0.0661412
0.08    0.0969477       -0.075107
0.09    0.0961524       -0.0839283
0.1     0.0952696       -0.0925977
0.11    0.094301        -0.101108
----    many lines ommitted    ----

可以将输出数据重定向到文本文件 data.txt 中,然后使用Python等脚本语言提取数据并画图显示。下面是实现该功能的参考代码:

import numpy as np
import matplotlib.pyplot as plt

lines = tuple(open("data.txt", 'r')) # 读取文件行到tuple中

rows = len(lines)
time  = np.zeros(rows)
theta = np.zeros(rows)
omega = np.zeros(rows)

for r in range(rows):
    [str1, str2, str3] = lines[r].split()
    time[r]  = float(str1)
    theta[r] = float(str2)
    omega[r] = float(str3)

plt.plot(time, theta, time, omega) # 角度和角速度变化
# plt.plot(theta, omega) # 相图
plt.show()

到此这篇关于用C++的odeint库求解微分方程的文章就介绍到这了,更多相关C++的odeint库求解微分方程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++实现矩阵对称正交化的示例代码

    1.python代码 import numpy as np import pandas as pd df=pd.DataFrame() df['fac_01']=(34, 45, 65) df['fac_02']=(56, 25, 94) print(df) print('------------------矩阵的特征跟D.和特征向量U-----------------------') D,U=np.linalg.eig(np.dot(df.T, df)) # 求矩阵的特征跟D.和特征向量U p

  • C++的异常处理一篇带你了解

    目录 一.背景 二.C++ 异常处理 三.抛出异常与捕获异常 四.catch(...)的作用 总结 一.背景 程序运行时常会碰到一些异常情况,例如: 做除法的时候除数为 0: 用 new 运算符动态分配空间时,空间不够导致无法分配: 访问数组元素时,下标越界:打开文件读取时,文件不存在. 这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃. 所谓"处理",可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行:也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存

  • C++中的构造函数详解

    目录 普通变量的初始化 构造函数 一定会生成默认构造函数吗? 防止隐式类型转换 赋值与初始化的区别 对象的计数 成员初始化的顺序 类的引用成员 构造函数使用注意事项 参考 总结 普通变量的初始化 当我们在定义一个变量不给它指定一个初始值时,这对于全局变量和局部变量来说结果会不一样.全局变量在程序装入内存时 就已经分配好空间,程序运行期间其地址不变,它会被初始化为全0(变量的每一位都为0).但是局部变量定义在函数内部,存储在栈上,当函数被调用时,栈会分配一部分空间来存储该局部变量(也就是只分配空间

  • 浅谈C++ 设计模式的基本原则

    先上银行类案例代码如下: #include<iostream> using namespace std; class BankWorker { public: void save() { cout << "存款" << endl; } void moveM() { cout << "取款" << endl; } void jiaofei() { cout << "缴费" &l

  • C++基于OpenCV实现手势识别的源码

    先给大家上效果图: 源码在下面 使用 RGB 值分割手部区域,即手部的 GB 值将与背景不同 或者使用边缘检测 或者 背景减法. 我这里使用了背景减法模型.OpenCV为我们提供了不同的背景减法模型,codebook   它的作用是对某些帧进行一段时间的精确校准.其中对于它获取的所有图像:它计算每个像素的平均值和偏差,并相应地指定框. 在前景中它就像一个黑白图像,只有手是白色的 用 Convex Hull 来找到指尖.Convex hull 基本上是包围手部区域的凸集. 包围手的红线是凸包.基本

  • C++实现广度优先遍历图

    本文实例为大家分享了C++实现广度优先遍历图的具体代码,供大家参考,具体内容如下 广度优先遍历 void bfs(int start, int parent[], int dist[], int seen[], int visited[]) { std::queue <int> q;//建立数据队列q int v; q.push(start); //让开始序列入栈 parent[start] = start; // 开始节点的父节点是开始节点 dist[start] = 0; // 初始化距离

  • C++的命名空间详解

    目录 C++ | C++命名空间 C++命名空间 定义命名空间 实例1: using 指令 实例2: 实例3: 不连续的命名空间 嵌套的命名空间 实例4: 实例5: 笔记: 实例6: 实例7: 总结 C++ | C++命名空间 C++命名空间 一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突. 当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突. 例如小李和小韩都参与了一个文件管理系统的开发,它们都定义了一个全局变量

  • C++类的特种函数生成机制详解

    目录 C++类的特种函数生成机制 规则 例子:A BUG 例子:std::mutex和std::thread 题外话:为什么std::mutex不可移动? 总结 C++类的特种函数生成机制 规则 参考Effective Morder C++上的说明: 默认构造函数:仅当类中不包含用户声明的构造函数时才生成. 析构函数:默认生成,当基类的析构函数为虚时,派生类的默认析构函数为虚函数. 拷贝构造函数:仅当类中不包含用户声明的拷贝构造函数时才生成.如果该类声明了移动操作,那么拷贝构造函数将被定义为删除

  • C++实现对象化的矩阵相乘小程序

    复习数学1的线性代数,矩阵相乘这块有点晕,想编个C++对象化的矩阵相乘小程序. 相乘部分 void sum(juzhen a, juzhen b, juzhen &c) { int s=0; for (int i = 1; i <= a.m1(); i++)//A矩阵的M for (int j = 1; j <= b.n1(); j++)//B矩阵的S { for (k0 = 1; k0 <= a.n1(); k0++)//a.n1也就是b.m1(a的n,b的n)[行向量*列向量

  • C++ 标准模板类详解

    目录 1 标准模板库 2.泛型编程 总结 1 标准模板库 STL提供了表示容器.迭代器.函数对象和算法的模板. 容器:类似数组存储若干值,切实同质的: 迭代器:遍历容器的对象,类似遍历数组的指针,广义指针: 算法:完成特定的任务: 函数对象:类对象或函数指针. 模板类 vector erase() 删除矢量中给定区间元素.接受两个迭代器参数(该参数定义了要删除的区间),迭代器1指向区间起始处,迭代器2指向区间终止处的后一个位置. // delete first and second elemen

随机推荐