ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MATLAB绘制Lorenz混沌系统三件套:相图、庞加莱截面与分岔图全解析

MATLAB绘制Lorenz混沌系统三件套:相图、庞加莱截面与分岔图全解析 如果你正在做非线性动力学方向的课题或者刚开始用MATLAB接触混沌系统一定绕不开“相图、庞加莱截面、分岔图”这三件套。单独画其中一张图并不难但要把三阶微分方程系统完整跑通用同一套流程把二维相图、三维相图、庞加莱截面图和分岔图都画出来并且保证图形能看清、能放进论文里面其实有不少容易踩的坑。这篇文章把我自己整理的一套MATLAB程序完整拆开讲一遍模型选用经典Lorenz系统代码可以在常见版本MATLAB中直接运行如果你要分析自己的三阶混沌系统只需要替换微分方程和对应参数即可整体框架完全通用。1. 三类图形分别解决什么问题1.1 相图看的是吸引子的几何结构我最早接触混沌系统时第一件事就是画相图。相图的价值在于直观它把系统状态在二维或三维空间中的轨迹直接画出来。对三维自治系统来说一条轨迹就是状态随时间流动的曲线长期运行后收敛到的集合就是吸引子。比如Lorenz系统在参数取经典值时三维相图会呈现一对形似蝴蝶翅膀的涡卷结构这个形状本身就是混沌动力学最著名的视觉符号。二维相图则是三维状态空间中的投影比如只画x-y、x-z或者y-z。二维图实现起来最简单但对于某些系统投影会丢失信息比如两条轨迹在投影平面上可能交叉实际上在三维空间里并没有相交。所以研究中我会两个都画先用三维相图看整体骨架再用二维相图确认具体投影面里的细节两者配合使用比单看一张图靠谱得多。1.2 庞加莱截面把连续流压缩成离散映射相图看多了你会发现问题连续轨迹看久了容易“糊”特别是长时间仿真后曲线层层叠叠很难判断吸引子的内部结构。这时候庞加莱截面就派上用场。庞加莱截面的做法是在状态空间中选一个合适的截面通常是一个平面然后只记录轨迹穿过这个平面的点。连续流变成了离散点集三维问题被压缩成二维映射看起来直观且能揭示很多相图上看不到的规律。比如Lorenz吸引子在截面上会呈现一段类似一维弧线的点云这说明混沌吸引子内部其实是有层叠结构的如果系统是周期运动截面上只会留下有限个点如果是拟周期运动截面上会形成闭合曲线。这些特征相图里几乎看不出来截面图里一目了然。1.3 分岔图回答“参数变化会发生什么”相图和截面图都是针对固定参数的而混沌研究里我们更关心系统随参数变化如何演变分岔图干的就是这件事。横轴是控制参数纵轴是轨迹在截面上的采样值每个参数下把稳态轨迹与截面的交点画出来就能看到系统从稳定平衡点到周期轨道再到多周期、混沌的完整演化路径。分岔图的价值在生产研究者眼里是“参数地图”我可以先扫描一遍大致知道哪些参数区间存在混沌哪些窗口是周期窗然后再回到相图和截面图上做精细分析。对于Lorenz系统来说以参数ρ为例从较小的值开始扫描到较大值能看到不动点失稳、产生极限环、再到倍周期级联和混沌的分岔路径一次画图就能理解系统宏观行为随参数的改变。2. 模型选择与理论支撑2.1 为什么把三阶微分方程系统作为起点连续时间自治系统要出现混沌状态空间维数至少需要三阶。原因很朴素二维相平面里的轨迹不能自相交因为微分方程解的唯一性禁止轨迹在有限时间内穿过同一点却走向不同方向。没有“折叠”机制系统就只能收敛到不动点或极限环。只有加入第三个维度轨迹才有足够的空间绕行、拉伸、折叠进而形成混沌吸引子。这也是为什么几乎所有经典混沌系统——Lorenz、Rössler、Chen、Chua电路——都是三阶微分方程。从算法实现角度看三阶系统的状态变量只有三个ODE求解器的计算开销小代码结构也清晰适合作为演示和研究的起点。理解了这一点再看程序里为什么反复强调“三阶”就不会觉得奇怪了。2.2 Lorenz系统的方程与参数含义我选用Lorenz系统作为默认测试对象方程如下dx/dt σ(y - x)dy/dt x(ρ - z) - ydz/dt xy - βz三个参数分别是σ、ρ、β。σ代表Prandtl数ρ与Rayleigh数相关β与几何尺寸相关。经典混沌参数取值是σ10、β8/3、ρ28这组参数下系统表现出双涡卷混沌吸引子。搭建程序的时候把这三个参数定义成主脚本里的变量换系统的时候只需要替换微分方程函数文件参数架构不用动。有一点值得解释截面和分岔图的实现会大量依赖参数ρ这里将ρ同时用于方程和截面位置。Lorenz系统的不稳定平衡点z坐标等于ρ-1选择zρ-1作为截面平面能让截面刚好穿过两个涡卷的中心区域得到的截面点结构最清晰。2.3 从连续流到离散映射的数学逻辑庞加莱截面的本质是Poincaré映射。设轨迹与截面的交点为P1、P2、P3……每个点由前一个点决定P_{n1}P(P_n)连续动力学被替换为离散迭代。这种降维处理不仅方便观察也为后续计算Lyapunov指数、分析周期轨道提供了基础。分岔图可以看成庞加莱截面在参数方向上的延拓每改变一次参数就计算一次离散映射然后把映射点沿参数轴画成一排。所以分岔图的质量很大程度上取决于两点一是截面选择是否合理二是每个参数下是否收集到足够多的稳态交点。理解了这条逻辑后面调参和排错就有方向了。3. MATLAB程序分步实现3.1 函数文件与ODE求解器配置先把微分方程封装成标准函数文件。MATLAB的ode45要求传入的函数返回状态导数我习惯单独创建一个lorenz_sys.mfunction dydt lorenz_sys(t, y, sigma, rho, beta) % 三阶Lorenz混沌系统 % y [x; y; z] dydt zeros(3,1); dydt(1) sigma * (y(2) - y(1)); dydt(2) y(1) * (rho - y(3)) - y(2); dydt(3) y(1) * y(2) - beta * y(3); end主脚本中调用ode45时我建议设置相对容差RelTol和绝对容差AbsTol到1e-6、1e-8左右。默认容差对普通问题可行但混沌系统对数值误差非常敏感轨迹稍微偏差就会在长时间积分后彻底偏离真实吸引子。严格来说混沌系统的长时间轨迹本身不可预测但吸引子的几何结构必须保持稳定误差控制不好会让吸引子变形相图和截面都会出现异常点。sigma 10; beta 8/3; rho 28; y0 [1; 1; 1]; options odeset(RelTol, 1e-6, AbsTol, 1e-8); [t, y] ode45((t, y) lorenz_sys(t, y, sigma, rho, beta), [0 200], y0, options);积分时长取200个单位主要目的是让轨迹越过初始瞬态充分收敛到吸引子上。如果只画相图时间短一点也可以但后续庞加莱截面和分岔图都需要足够多的穿越采样点因此我通常把总时长控制在100到200之间。3.2 二维与三维相图的绘制代码直接用ode45返回的数据绘图会在图里混入一段从初始点到吸引子之间的过渡轨迹看起来像一根“多余的天线”。我的做法是两段积分先用较长时长让系统收敛然后从收敛状态的末端出发重新积分一段较短时间只绘制第二段数据。[t1, y1] ode45((t, y) lorenz_sys(t, y, sigma, rho, beta), [0 200], y0, options); [t2, y2] ode45((t, y) lorenz_sys(t, y, sigma, rho, beta), [0 80], y1(end,:), options); figure(Color, w); plot3(y2(:,1), y2(:,2), y2(:,3), LineWidth, 0.8); xlabel(x); ylabel(y); zlabel(z); title(Lorenz系统三维相图\rho28); grid on; view(30, 20);二维相图的画法类似只是去掉一个坐标轴。我会用一个子图窗口同时展示三组投影figure(Color, w); subplot(1,3,1); plot(y2(:,1), y2(:,2)); xlabel(x); ylabel(y); title(x-y投影); subplot(1,3,2); plot(y2(:,1), y2(:,3)); xlabel(x); ylabel(z); title(x-z投影); subplot(1,3,3); plot(y2(:,2), y2(:,3)); xlabel(y); ylabel(z); title(y-z投影);这样处理之后二维相图里不会有瞬态轨迹干扰图形的轮廓也干净很多。线下实际操作时我还会把每张图的x轴和y轴范围设成一致方便比较不同投影面之间的相对关系。这个细节经常被忽略但对理解三维吸引子的空间构型很有帮助。3.3 庞加莱截面方向判据与插值采样庞加莱截面的核心代码并不复杂但有两个细节必须处理正确一是穿越方向的判断二是交点坐标的计算。穿越方向说的是轨迹从截面一侧穿到另一侧的方向。我不建议把两个方向都记录因为两个方向的交点叠加在一起会形成两套映射图形混乱。一般情况下只取一个方向比如z从小于截面值变到大于截面值也就是正向穿越。交点坐标的计算不能用最近的时间步点直接代替因为ode45返回的是离散时间步数据点几乎不可能恰好落在截面上。我采用线性插值在两个相邻时间步之间找到轨迹穿越截面的精确位置精度足够且实现简单。plane_z rho - 1; % 截面位置 xp []; yp []; for i 2:length(t) if y(i-1,3) plane_z y(i,3) plane_z % 线性插值求穿越点的 x 和 y frac (plane_z - y(i-1,3)) / (y(i,3) - y(i-1,3)); x_cross y(i-1,1) frac * (y(i,1) - y(i-1,1)); y_cross y(i-1,2) frac * (y(i,2) - y(i-1,2)); xp(end1) x_cross; yp(end1) y_cross; end end figure(Color, w); plot(xp, yp, ., MarkerSize, 5); xlabel(x); ylabel(y); title(Lorenz系统庞加莱截面z\rho-1平面);这里把截面位置选为zρ-1原因前面提过这个平面穿过两个涡卷中心。你可以把plane_z改成其他常数试一下比如z20或z30图形形态会明显变化这也是理解截面选择对分析结果影响的好练习。3.4 分岔图参数扫描与瞬态丢弃分岔图的实现思路是循环修改参数ρ对每个ρ值都做一次完整积分然后只保留稳态部分的截面穿越点。我推荐下面的结构sigma 10; beta 8/3; rho_list 4:0.1:32; steady_t 100; % 只记录稳态之后的数据 total_t 180; figure(Color, w); hold on; for rho rho_list y0 [1; 1; 1]; [t, y] ode45((t, y) lorenz_sys(t, y, sigma, rho, beta), [0 total_t], y0, options); plane_z rho - 1; x_cross []; for i 2:length(t) if y(i-1,3) plane_z y(i,3) plane_z t(i) steady_t frac (plane_z - y(i-1,3)) / (y(i,3) - y(i-1,3)); x_cross(end1) y(i-1,1) frac * (y(i,1) - y(i-1,1)); end end rho_plot rho * ones(size(x_cross)); plot(rho_plot, x_cross, ., MarkerSize, 2, Color, [0.1 0.3 0.7]); end xlabel(\rho); ylabel(x_{cross}); title(Lorenz系统分岔图); box on;有几个地方需要特别说明。steady_t的设定是分岔图质量的保障把初始瞬态对应的穿越点全部剔除否则分岔图开头部分会出现一段毫无规则的乱点掩盖真实的稳定分支。y0在每次循环里都固定为[1;1;1]这是为了保证所有参数点从相同的初始条件出发不同参数之间的结果具有可比性。如果你希望加速收敛也可以用上一次积分的末态作为下一次的初值但这样会引入历史依赖某些分支上的对称性可能会被破坏我建议初学时采用固定初始条件画面更干净。分岔图的另一个常见问题是采样点过多导致显示性能下降。这里的代码每轮循环只记录截面的x坐标点集规模在可控范围内。如果你把范围扩大到ρ40甚至更大建议把MarkerSize设为1并且只保存x_cross数组而不要在循环内反复绘图所有点收集完后一次性绘制速度会快很多。4. 常见问题与排查技巧实录4.1 分岔图全是毛刺问题多半出在瞬态剔除我见过最多的失败案例就是分岔图整体呈“雾状”看不出清晰的分支结构。绝大多数情况下这是没有剔除瞬态造成的。混沌系统的轨迹从初始点到达吸引子需要时间这段时间里的穿越点不属于系统的长期行为如果把它们也画进去分岔图自然是一片乱麻。解决方法是把穿越点的采样限定在tsteady_t之后。这个steady_t数值需要足够大一般取总积分时长的三分之一到一半比较稳妥。还有一点容易被忽略如果积分容差设置太松轨迹的长期行为本身就有误差截面的穿越点也会出现漂移。遇到分岔图毛刺我会先用ode45的容差检查一遍再检查稳态窗口。4.2 截面点乱飞方向判断没写对如果你发现庞加莱截面的点分布明显分成两套结构交叉重叠十分严重先检查是不是把正向穿越和负向穿越都记录了。有的代码如下所示会这样写if (y(i-1,3) - plane_z) * (y(i,3) - plane_z) 0这种写法不区分方向两个方向的穿越点全部记录。对某些系统来说正反两个方向的映射可能是对称的画出来还能看但对Lorenz这类系统两套点混在一起会让截面图变得杂乱失去离散映射的意义。我的习惯是只记录正向穿越。如果你确实需要分析两个方向各自的映射那就分开存储、分开绘图而不是混在一个坐标系里。另一个潜在问题是截面与轨迹近似相切导致某些穿越点非常靠近看起来像一堆重叠的点这种情况通常需要调整截面位置或者缩小ODE步长以获得更精细的穿越检测。4.3 分岔图的周期窗口“缺一块”怎么办Lorenz系统在混沌区间里会嵌入一些周期窗口比如ρ在30附近时会出现明显的周期窗。如果你画出来的分岔图在这些位置没有显示清晰的周期结构大概率是参数扫描步长太大漏掉了窗口。改进办法是把rho_list的步长从0.1缩小到0.02甚至0.01同时适当增大单次积分的总时长。周期窗口内的穿越点数量会比混沌区间少很多如果采样点不足窗口看起来就像空白。我试过一个实用技巧先在0.2的粗步长下快速跑一遍确定大致分岔位置再在感兴趣区间加密扫描这样既节省时间又不会漏掉关键结构。4.4 程序跑得太慢怎么优化三阶系统本身计算量不算大但分岔图要循环几十到上百次完整积分每次积分里还要逐点扫描穿越慢是肯定的。我做了三个优化效果很明显。一是预分配数组。不要像我第一版代码那样在循环里不断用end1动态增长数组预分配一个足够大的数组每次记录到指定位置结束后再截断速度提升明显。二是减少循环内重复计算。比如ode45的输出t和y可以先整体判断穿越区间再用向量化方式处理避免for循环逐点判断。三是合理选择积分时长不是为了采样点越多越好采样点到一定数量后再多只会拖慢绘图速度对结构判断没有本质帮助。如果机器性能足够也可以用parfor把不同ρ值的积分并行化但要注意分岔图的数据量可能很大并行时每个worker都会保存一份数据内存有限的情况下反而会出问题。我通常先跑串行版本验证逻辑再在需要长时间扫描时考虑并行。4.5 运行环境与常见启动问题速查代码本身写对了但MATLAB启动异常也会耽误时间。这里整理几个常见的环境问题都是我自己或身边同事实际遇到的。如果启动时提示license相关的错误比如license manager error -8或-1多半是主机信息发生变化license与hostid不匹配需要检查激活文件。另一种常见情况是MATLAB可以启动但脚本报错说找不到某个函数这通常是当前文件夹或搜索路径没有包含对应.m文件用addpath把脚本所在目录加进去即可。还有同事遇到过运行Simulink或者外部工具时提示“MATLAB not found”这种问题一般出在工具与MATLAB的关联路径配置上在外部软件里手动指定MATLAB安装路径就能解决。5. 出图细节让图形达到论文级5.1 配色、视角与线型配置很多初学者画完图直接截图打印出来后锯齿严重、颜色失真。我建议在出图前完成几项设置图窗背景设为白色坐标轴字号统一线宽适当加粗。三维相图里view(30, 20)是比较常规的视角你也可以用view(135, 25)从另一个方向观察有时能看到双涡卷的层叠关系。相图的轨迹线我习惯用单一深蓝色或黑色细线宽0.8到1.2不需要彩色渐变因为混沌轨迹曲线密度大颜色太多反而显得混乱。庞加莱截面和分岔图则用点图MarkerSize在2到5之间。这里有个容易犯的错误分岔图的点如果太多MarkerSize又偏大整个图会变成一块实心色块什么都看不清。我会在点数多时把MarkerSize设为1或2并用颜色较淡的MarkerEdgeColor来降低视觉密度。5.2 矢量导出与数据保存导出图片建议用print命令而不是截图这样才能得到矢量格式。我用得比较多的是print(-depsc2, -r300, lorenz_phase3d);这条命令会将当前图窗导出为300dpi的EPS文件出版社和论文投稿都认这种格式。如果只是日常记录用exportgraphics导出PNG或PDF也很方便exportgraphics(gcf, lorenz_poincare.pdf, ContentType, vector);除了图像原始数据也要保存。庞加莱截面的交点坐标和分岔图的采样点建议存成.mat或CSV方便后续重新绘图和分析。我通常会这样save(poincare_data.mat, xp, yp, t);这样即使换了电脑、换了MATLAB版本只要加载.mat文件几分钟内就能复现论文里的图不用重算一遍。这个习惯看起来简单但在修改参数、重复实验的时候能省下大量时间。6. 一点个人实操体会6.1 我的代码工作流这套程序写了不止一遍最近整理的时候我才意识到自己已经形成了一套固定工作流先确定方程和参数再用粗糙参数快速验证稳态存在然后才写截面和分岔图代码。每次分析一个新的三阶混沌系统我都建议先画一张三维相图观察吸引子的大致形状再根据吸引子的对称性和平衡点位置选择截面。相图没看明白就急着画分岔图往往会在截面位置和穿越方向上反复踩坑最后连错误的来源都找不到。另外我会在代码里把微分方程函数与主脚本分开主脚本只负责参数设置和调用。这样换系统的时候只需替换方程函数文件绘图和后处理逻辑完全复用非常省时间。你如果还不会写函数句柄传参建议先花十分钟看看匿名函数和函数文件的区别这是用MATLAB做非线性动力学分析的基本功。6.2 后续可以怎么扩展跑通这套程序之后很多方向都能继续延伸。最直接的是把Lorenz方程换成Rössler或Chen系统观察吸引子形状和截面图的变化。进一步可以做Lyapunov指数谱与分岔图相互验证确认混沌区间和周期窗口。如果你想深入做控制还可以在ode45仿真过程中加入控制器信号比较控制前后的相图和截面图。我个人最推荐先做一件事把分岔图某个特定参数窗口放大比如ρ从24到28这一段你会看到从周期倍化到混沌的细致路径这比看整幅大图更直观也是理解混沌产生机制最快的方式。编程上的论文性调用就用现状直接传达即可。
返回列表