实现概述

洛伦兹吸引子是一组微分方程,我们可以用 c++ boost odeint 库来解微分方程。

2024 年 7 月 13 日

首页: 发一格 @cpp

数学上的描述

洛伦兹吸引子微分方程组

\begin{align} & \dot{x} = \sigma (y-x) &\\& \dot{y} = x (\rho - z) -y &\\& \dot{z} = xy - \beta z & \end{align} 它们都是时间 t 的导数
\begin{align} & \sigma, \rho, \beta \text{常见的取值为:} &\\& \begin{bmatrix} \sigma \\ \rho \\ \beta \end{bmatrix} = \begin{bmatrix} 10 \\ 25 \\ \frac{8}{3} \end{bmatrix} & \end{align}
注:把一个点写在字母的正上方是导数在动力系统中的写法。

格式

要把表达式转化成可以对照着写 c++ 代码 (boost odeint) 的格式,减少精神内耗:
\begin{align} & \sigma = 10 \quad \rho = 25 \quad \beta = \frac{8}{3} &\\& \begin{bmatrix} x \\ y \\ z \end{bmatrix} \longrightarrow \begin{bmatrix} \dot{x} \\ \dot{y} \\ \dot{z} \end{bmatrix} = \begin{bmatrix} \sigma (y-x) \\ x (\rho - z) - y \\ xy - \beta z \end{bmatrix} & \end{align}

c++ 代码

描述

编写 boost odeint 系统函数用于调微分方程组:

class lorenz_class
{
public:
	void operator()(const state_type & in, state_type & diff, const double t) const
	{
	}
};
\begin{align} & \text{这里的 in 可以存储三个函数相关的值:} &\\& x, y, z &\\& \text{diff 可以存储对应的微分三个值:} &\\& \dot{x}, \dot{y}, \dot{z} &\\& &\\& \dot{x}, \dot{y}, \dot{z} \text{都是时间 t 的导数} & \end{align}
注意变量命名引起的混淆,如果把 in 写成 x, 那么可以理解它存储的三个数学值为 x1, x2, x3, 跟 x, y, z 对应; diff 也可以写成 dxdt, 命名不会改变计算规则。

微分方程

diff[0] = sigma * (in[1] - in[0]);           // x' = sigma * (y-x)
diff[1] = in[0] * (rho - in[2]) - in[1];     // y' = x * (rho - z) - y
diff[2] = in[0] * in[1] - beta * in[2];      // z' = xy - beta * z

初始状态值的选择

我们可以选取空间中的某个点作为初始状态值,即可生成洛伦兹吸引子蝴蝶,但是,要避免选到吸引点,这样的点会导致无论迭代多少次,都不会逃逸吸引点,通常原点极有可能成为吸引点,所以不要选择原点作为初始状态值。

完整的 c++ 代码

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

namespace odeint = boost::numeric::odeint;
using value_type = double;
using time_type = value_type;
using state_type = std::vector<value_type>;

constexpr value_type sigma = 10;
const value_type rho = 25;
const value_type beta = 8.0/3;

// 洛伦兹类
class lorenz_class
{
public:
	// 微分方程
	void operator()(const state_type & in, state_type & diff, const time_type t) const
	{
		diff[0] = sigma * (in[1] - in[0]);		// x' = sigma * (y-x)
		diff[1] = in[0] * (rho - in[2]) - in[1];		// y' = x * (rho - z) - y
		diff[2] = in[0] * in[1] - beta * in[2];		// z' = x*y - beta * z
	}
	// 观察函数
	void operator()(const state_type & result, const time_type t) const
	{
		std::cout << std::setw(10) << result[0] << std::setw(10) << result[1] << std::setw(10) << result[2]
			<< std::endl;
	}
};

int main()
{
	auto lorenz = lorenz_class{};	// 洛伦兹对象
	auto xyz = state_type{1, 1, 1};	// 初始状态
	int steps = odeint::integrate_const(
		odeint::runge_kutta4<state_type, value_type>{},
		lorenz,	// 微分方程
		xyz,
		time_type{0},	// 起始时间
		time_type{10},	// 结束时间
		time_type{0.1},	// dt 间隔
		lorenz	// 观察函数
	);
	std::cout << "steps: " << steps << std::endl;
}

编译运行输出:

$ g++ lorentz.cpp -std=c++23 -o lorentz

$ ./lorentz

         1         1         1
   2.06572   3.83607   1.05343
   5.56918   11.1022   3.23336
    13.817   23.2773    18.203
   14.9366   8.52487   40.1873
   5.47706  -4.22664   31.4177
 -0.970417  -4.59924   23.4589
   -3.2924  -5.22568     18.98
  -5.28421  -7.76331   17.0037
   -8.2712  -11.7431   18.9597
  -11.2437  -13.0502   25.8689
  -10.5808  -7.76616   30.2315
  -7.04623  -3.54172    27.227
  -4.58147  -3.25655   22.4171
  -4.25916  -4.79911   18.6409
  -5.61984  -7.76344   16.9502
  -8.47063  -11.8382   19.1635
  -11.3022   -12.902   26.1548
  -10.4551  -7.52194   30.1788
  -6.91835  -3.50379   27.0164
  -4.55712  -3.32719   22.2464
  -4.31587  -4.92683   18.5556
  -5.74402  -7.95299   17.0076
  -8.63419  -11.9909   19.4579
  -11.3435  -12.7216   26.4987
  -10.2971  -7.24131   30.1095
  -6.76799  -3.46302   26.7702
  -4.52877  -3.40915   22.0471
  -4.38181  -5.07592   18.4565
  -5.88888  -8.17353   17.0782
  -8.82133  -12.1578   19.8055
  -11.3798  -12.4973   26.8791
  -10.1112  -6.92899   30.0135
  -6.59986  -3.42281    26.488
  -4.49941  -3.50373   21.8206
  -4.45896  -5.24804   18.3461
  -6.05613  -8.42591   17.1675
  -9.03088  -12.3322   20.2117
  -11.4049  -12.2215   27.2867
  -9.89587  -6.59101   29.8833
  -6.41701  -3.38733   26.1701
  -4.47172  -3.61278   21.5692
  -4.54949  -5.44522   18.2283
  -6.24756  -8.71064   17.2823
  -9.26079  -12.5056    20.682
  -11.4117  -11.8869   27.7094
  -9.64971   -6.2349   29.7114
  -6.22313  -3.36101   25.8173
  -4.44878  -3.73844   21.2956
  -4.65592  -5.66984   18.1078
  -6.46507  -9.02778   17.4309
  -9.50793  -12.6669    21.221
  -11.3923  -11.4876   28.1324
  -9.37211  -5.86939   29.4907
  -6.02249  -3.34834   25.4308
  -4.43398  -3.88327   21.0031
   -4.7812  -5.92476   17.9901
  -6.71074  -9.37665   17.6231
  -9.76781  -12.8022   21.8324
  -11.3384  -11.0196   28.5379
  -9.06323  -5.50416   29.2151
  -5.81972  -3.35373   25.0121
  -4.43098  -4.05029   20.6951
  -4.92875  -6.21334   17.8821
  -6.98668  -9.75555   17.8708
  -10.0342  -12.8944    22.518
  -11.2418  -10.4817   28.9061
  -8.72426  -5.14935   28.8796
  -5.61975  -3.38134   24.5629
  -4.44365  -4.24302   20.3756
  -5.10246  -6.53945   17.7919
  -7.29484  -10.1611   18.1876
  -10.2988  -12.9237   23.2757
  -11.0942  -9.87663   29.2152
  -8.35777   -4.8153   28.4808
  -5.42772  -3.43499    24.085
  -4.47605  -4.46543   20.0489
  -5.30658  -6.90717   17.7291
  -7.63651   -10.587   18.5885
  -10.5503  -12.8671   24.0978
  -10.8887  -9.21232   29.4424
  -7.96822  -4.51223   28.0171
  -5.24898  -3.51814   23.5807
  -4.53237  -4.72181   19.7204
  -5.54547  -7.32016   17.7052
  -8.01142  -11.0226   19.0893
  -10.7744   -12.701   24.9684
    -10.62  -8.50304   29.5658
  -7.56248  -4.25004   27.4895
  -5.08924  -3.63386   23.0539
  -4.61691  -5.01653   19.3966
  -5.82326  -7.78079   17.7339
  -8.41663  -11.4513   19.7046
  -10.9535   -12.403   25.8604
  -10.2857  -7.76991   29.5659
  -7.15008  -4.03791   26.9024
  -4.95463  -3.78505   22.5102
  -4.73417  -5.35387   19.0867
  -6.14344  -8.28893   17.8317
   -8.8452  -11.8485   20.4454
   -11.068  -11.9572   26.7351
steps: 100

输出数据

输出数据可以完整的发给 c++ sfml 图形 api 渲染成 3D 图像,可以逐步发送给 c++ sfml api,实时渲染,做成动画,绘图这部分暂不放出,下次待续。