1. 从“猫捉老鼠”到“四人追逐”一个经典动力学问题的魅力在数学建模和动力学模拟的领域里追逐问题一直是一个充满趣味和挑战的经典课题。你可能听说过“狗追兔子”或者“猫捉老鼠”的简化模型但今天我们要探讨的是一个更复杂、也更优雅的变体——四人追逐问题。想象一下在一个正方形的四个顶点上分别站着四个人A、B、C、D。游戏规则很简单A始终朝着B的方向跑B始终朝着C的方向跑C始终朝着D的方向跑而D则始终朝着A的方向跑。他们的速度大小恒定且相同。那么这四个人最终会相遇吗他们的运动轨迹会是什么样子这个问题看似简单却完美地融合了微分方程、数值计算和几何直观是学习数学建模和MATLAB模拟的绝佳案例。对于学习数学建模的同学或者对用代码“解构”物理世界感兴趣的工程师来说这个问题提供了一个从问题抽象到代码实现再到结果可视化的完整闭环。它不要求你具备高深的偏微分方程理论但需要你理解如何将一个连续的动力学过程通过离散化的思想用计算机一步步“演算”出来。这正是MATLAB这类工具大显身手的地方。通过这个项目你不仅能学会如何用MATLAB求解常微分方程组更能掌握数值模拟的核心思想将时间切片用“此刻”的状态去预测“下一刻”的状态。接下来我将带你一步步拆解这个问题并用MATLAB构建一个完整的模拟程序最后我们还会一起分析那些有趣且反直觉的模拟结果。2. 问题建模将文字描述转化为数学方程任何模拟的第一步都是为现实世界或思想实验建立一个可计算的数学模型。对于四人追逐问题我们需要用严谨的数学语言来定义“追逐”这个行为。2.1 定义系统状态与核心假设首先我们明确系统的状态。假设四个人在一个二维平面上运动。在任意时刻t他们的位置可以用坐标表示A:(xA(t), yA(t))B:(xB(t), yB(t))C:(xC(t), yC(t))D:(xD(t), yD(t))这是我们系统的状态变量一共8个4个人的x和y坐标。接下来是核心的运动规则假设速度恒定每个人的运动速率速度的大小v是一个常数且四人相同。这是问题的一个关键简化使得模型可控。追逐方向每个人的速度方向始终指向其追逐目标当前的位置。这是模型的核心动力学规则。初始位置通常我们假设初始时刻t0时四人分别位于一个边长为L的正方形的四个顶点上。例如A(0,0), B(L,0), C(L,L), D(0,L)。这个对称的初始条件会让轨迹产生优美的对称性。2.2 推导速度向量与微分方程“速度方向始终指向目标”这句话需要用向量来表达。以A为例它的目标是B。那么在时刻t从A指向B的向量是(xB - xA, yB - yA)。这个向量指明了A应该面对的方向。但是速度是一个向量它有方向也有大小。我们已知大小是v方向与向量(xB - xA, yB - yA)相同。为了得到一个方向相同、长度为v的向量我们需要对方向向量进行“归一化”处理。归一化将一个向量除以其长度模得到的是一个长度为1、方向不变的单位向量。向量(xB - xA, yB - yA)的长度是sqrt((xB - xA)^2 (yB - yA)^2)。所以对应的单位向量是((xB - xA)/d_AB, (yB - yA)/d_AB)其中d_AB sqrt((xB - xA)^2 (yB - yA)^2)。那么A的速度向量(vxA, vyA)就等于这个单位向量乘以速率vvxA v * (xB - xA) / d_AB vyA v * (yB - yA) / d_AB而速度就是位置对时间的导数。所以我们得到了描述A位置变化的微分方程dxA/dt v * (xB - xA) / d_AB dyA/dt v * (yB - yA) / d_AB同理我们可以写出其他三人的微分方程B追逐CdxB/dt v * (xC - xB) / d_BC dyB/dt v * (yC - yB) / d_BC 其中 d_BC sqrt((xC - xB)^2 (yC - yB)^2)C追逐DdxC/dt v * (xD - xC) / d_CD dyC/dt v * (yD - yC) / d_CD 其中 d_CD sqrt((xD - xC)^2 (yD - yC)^2)D追逐AdxD/dt v * (xA - xD) / d_DA dyD/dt v * (yA - yD) / d_DA 其中 d_DA sqrt((xA - xD)^2 (yA - yD)^2)这样我们就得到了一个由8个一阶常微分方程ODEs耦合而成的方程组。所谓“耦合”意思是每个方程右边的表达式里都包含了其他未知量其他人的坐标它们必须被联立求解。这个方程组没有简单的解析解即我们无法写出一个像xA(t) ...这样的干净公式这正是我们需要借助MATLAB进行数值模拟的原因。注意一个关键的边界情况——当两个人无限接近时距离d会趋近于0导致速度计算中的分母为0引发数值计算错误如NaN或无穷大。在实际编程中我们必须处理这种情况例如设置一个最小距离阈值当距离小于该阈值时认为他们已经“相遇”速度置零或者直接终止模拟。这是数值模拟中常见的稳定性处理技巧。3. MATLAB实战构建数值模拟程序有了数学模型我们就可以用MATLAB将它实现。数值求解微分方程组的核心思想是“离散化时间”我们不去计算连续时间t上的精确解而是计算在一系列离散时间点t0, t1, t2, ...上的近似解。MATLAB提供了强大的ODE求解器如ode45非常适合处理这类问题。3.1 编写微分方程函数首先我们需要定义一个函数这个函数的输入是当前时间t和当前的状态向量Y输出是状态向量的一阶导数dYdt。这是ODE求解器要求的固定格式。我们的状态向量Y需要包含8个变量。一个合理的排列顺序是Y [xA; yA; xB; yB; xC; yC; xD; yD]。那么导数向量dYdt也应按相同顺序排列dYdt [vxA; vyA; vxB; vyB; vxC; vyC; vxD; vyD]其中的速度分量就是我们上一节推导的公式。下面是一个示例函数four_chase_odes.mfunction dYdt four_chase_odes(t, Y, v) % 输入 % t: 时间ODE求解器要求本例中方程不显含t故未使用 % Y: 8x1 状态向量 [xA; yA; xB; yB; xC; yC; xD; yD] % v: 追逐速度常数 % 输出 % dYdt: 8x1 导数向量 [dxA/dt; dyA/dt; ...] % 从状态向量Y中提取每个人的坐标 xA Y(1); yA Y(2); xB Y(3); yB Y(4); xC Y(5); yC Y(6); xD Y(7); yD Y(8); % 计算相对距离并避免除零错误 eps 1e-6; % 设置一个很小的阈值 d_AB sqrt((xB - xA)^2 (yB - yA)^2) eps; d_BC sqrt((xC - xB)^2 (yC - yB)^2) eps; d_CD sqrt((xD - xC)^2 (yD - yC)^2) eps; d_DA sqrt((xA - xD)^2 (yA - yD)^2) eps; % 根据追逐规则计算每个人的速度分量导数 dxA_dt v * (xB - xA) / d_AB; dyA_dt v * (yB - yA) / d_AB; dxB_dt v * (xC - xB) / d_BC; dyB_dt v * (yC - yB) / d_BC; dxC_dt v * (xD - xC) / d_CD; dyC_dt v * (yD - yC) / d_CD; dxD_dt v * (xA - xD) / d_DA; dyD_dt v * (yA - yD) / d_DA; % 组装导数向量 dYdt [dxA_dt; dyA_dt; dxB_dt; dyB_dt; dxC_dt; dyC_dt; dxD_dt; dyD_dt]; end实操心得关于eps的选取这里的eps是一个很小的正数我用了1e-6目的是防止当两人距离非常近时分母为零。这个值不能太大否则会扭曲速度方向也不能太小要能有效避免浮点数下溢。1e-6是一个在物理模拟中常用的经验值。更严谨的做法是当距离小于某个阈值如1e-3时直接判定相遇将所有导数设为零。3.2 配置求解器与初始条件接下来我们编写主脚本main_simulation.m来调用求解器并设置模拟参数。% main_simulation.m clear; close all; clc; % 1. 设置模拟参数 v 1.0; % 追逐速度设为1方便理解距离单位/时间单位 L 10; % 正方形边长 tspan [0, 50]; % 模拟时间范围 [起始时间 结束时间]。需要足够长以观察相遇过程。 % 2. 定义初始状态向量 Y0 % 初始位置A(0,0), B(L,0), C(L,L), D(0,L) Y0 [0; 0; L; 0; L; L; 0; L]; % 3. 使用 ode45 求解微分方程组 % 注意我们需要将速度参数 v 传递给 ODE 函数使用匿名函数实现参数传递 [t, Y] ode45((t,Y) four_chase_odes(t, Y, v), tspan, Y0); % 4. 从结果中提取每个人的轨迹 % Y 是一个矩阵每一行对应一个时间点的8个状态列数等于时间点个数。 xA_traj Y(:, 1); yA_traj Y(:, 2); xB_traj Y(:, 3); yB_traj Y(:, 4); xC_traj Y(:, 5); yC_traj Y(:, 6); xD_traj Y(:, 7); yD_traj Y(:, 8);这里有几个关键点ode45求解器它是MATLAB中求解非刚性常微分方程的首选工具采用Runge-Kutta (4,5)公式能自动调整步长平衡精度和效率。对于这个问题完全够用。参数传递我们的ODE函数需要速度参数v。通过(t,Y) four_chase_odes(t, Y, v)创建了一个匿名函数将v值“冻结”并传递给求解器。时间范围tspan设为[0, 50]是一个估计。理论上四人从正方形顶点出发最终会在中心相遇。我们可以先跑一次如果发现他们在50秒前就已非常接近可以缩短时间如果还没接近就延长。也可以通过监测四人间的平均距离来自动终止模拟。3.3 可视化让轨迹动起来数值结果是一堆数据点可视化能让我们直观理解运动过程。我们可以绘制静态轨迹图也可以制作动态模拟。静态轨迹图% 绘制静态轨迹图 figure(Position, [100, 100, 800, 800]); hold on; grid on; axis equal; box on; % 绘制四个人的轨迹线 plot(xA_traj, yA_traj, r-, LineWidth, 1.5, DisplayName, A); plot(xB_traj, yB_traj, g-, LineWidth, 1.5, DisplayName, B); plot(xC_traj, yC_traj, b-, LineWidth, 1.5, DisplayName, C); plot(xD_traj, yD_traj, m-, LineWidth, 1.5, DisplayName, D); % 标记起点和终点 plot(Y0(1), Y0(2), ro, MarkerSize, 10, MarkerFaceColor, r); plot(Y0(3), Y0(4), go, MarkerSize, 10, MarkerFaceColor, g); plot(Y0(5), Y0(6), bo, MarkerSize, 10, MarkerFaceColor, b); plot(Y0(7), Y0(8), mo, MarkerSize, 10, MarkerFaceColor, m); % 标记终点取最后一个时间点的位置 plot(xA_traj(end), yA_traj(end), rs, MarkerSize, 12, LineWidth, 2); plot(xB_traj(end), yB_traj(end), gs, MarkerSize, 12, LineWidth, 2); plot(xC_traj(end), yC_traj(end), bs, MarkerSize, 12, LineWidth, 2); plot(xD_traj(end), yD_traj(end), ms, MarkerSize, 12, LineWidth, 2); xlabel(X 位置); ylabel(Y 位置); title(四人追逐问题运动轨迹); legend(Location, best); hold off;动态模拟动画 动画能更好地展示追逐过程。我们可以每隔若干步取一帧数据来播放。% 制作动态模拟动画 figure(Position, [100, 100, 800, 800]); hold on; grid on; axis equal; xlim([-1, L1]); ylim([-1, L1]); % 固定坐标轴范围 xlabel(X); ylabel(Y); title(四人追逐动态模拟); % 预先绘制轨迹线浅色背景 hA_line plot(xA_traj(1), yA_traj(1), r-, LineWidth, 0.5); hB_line plot(xB_traj(1), yB_traj(1), g-, LineWidth, 0.5); hC_line plot(xC_traj(1), yC_traj(1), b-, LineWidth, 0.5); hD_line plot(xD_traj(1), yD_traj(1), m-, LineWidth, 0.5); % 创建代表四个人的点标记 hA_point plot(xA_traj(1), yA_traj(1), ro, MarkerSize, 10, MarkerFaceColor, r); hB_point plot(xB_traj(1), yB_traj(1), go, MarkerSize, 10, MarkerFaceColor, g); hC_point plot(xC_traj(1), yC_traj(1), bo, MarkerSize, 10, MarkerFaceColor, b); hD_point plot(xD_traj(1), yD_traj(1), mo, MarkerSize, 10, MarkerFaceColor, m); % 设置动画更新步长避免帧数过多 step max(1, floor(length(t) / 200)); % 大约取200帧 fps 30; % 帧率 pause_time step / (fps * (t(2)-t(1))); % 根据实际时间间隔计算暂停时间 for k 1:step:length(t) % 更新轨迹线画到当前点 set(hA_line, XData, xA_traj(1:k), YData, yA_traj(1:k)); set(hB_line, XData, xB_traj(1:k), YData, yB_traj(1:k)); set(hC_line, XData, xC_traj(1:k), YData, yC_traj(1:k)); set(hD_line, XData, xD_traj(1:k), YData, yD_traj(1:k)); % 更新点的位置 set(hA_point, XData, xA_traj(k), YData, yA_traj(k)); set(hB_point, XData, xB_traj(k), YData, yB_traj(k)); set(hC_point, XData, xC_traj(k), YData, yC_traj(k)); set(hD_point, XData, xD_traj(k), YData, yD_traj(k)); drawnow; % 刷新图形 pause(pause_time); % 控制播放速度 end踩坑提醒动画卡顿与性能如果时间点数据t非常密集ode45默认精度会产生很多点逐点更新动画会极其缓慢。上面代码中的step变量就是用来“跳帧”的我们只绘制大约200帧保证了流畅性。另一个技巧是使用animatedline对象来绘制轨迹它在处理长线条时效率更高。对于更复杂的模拟可以考虑将数据先保存下来然后用VideoWriter生成视频文件这样更稳定。4. 结果分析与模型拓展运行上述代码后我们会得到一幅优美的轨迹图。四条螺旋线从正方形的四个角出发最终汇聚于中心。这个结果本身就很迷人但作为建模者我们不能止步于此还要深入分析其背后的规律并思考如何拓展模型。4.1 轨迹的数学性质与相遇时间从模拟结果中我们可以观察到几个有趣的数学性质对称性由于初始条件和运动规则的完美对称四个人的轨迹是等价的只是经过了旋转。A的轨迹和C的轨迹关于中心点旋转180度B和D亦然。等时性虽然他们的路径是曲线但根据对称性他们将在同一时刻到达中心点。这个时间可以估算吗对于正方形边长为L速度为v的情况有人通过分析证明相遇时间T L / v。你可以用模拟结果验证一下计算他们位置非常接近比如平均距离小于0.01*L的时刻看看是否接近L/v。在我们的例子中L10v1所以T理论上为10。模拟结果可能会略大于10因为数值积分有微小误差且“非常接近”的判断标准也有影响。路径是等角螺线对数螺线吗这是一个常见的误解。许多人第一眼会觉得这螺旋线像阿基米德螺线或对数螺线。实际上在四人追逐问题中每个人的路径是一条“追逐曲线”pursuit curve它并不是标准的等角螺线。等角螺线的性质是切线与径向的夹角为常数而在这个问题中这个夹角是在不断变化的。你可以通过计算验证取A的轨迹上一点计算位置向量从中心指向A和速度向量切线方向的夹角你会发现它并非恒定。我们可以写一段简单的代码来验证相遇时间和计算夹角% 分析模拟结果验证相遇时间与路径性质 % 计算四人位置的中心质心 center_x (xA_traj xB_traj xC_traj xD_traj) / 4; center_y (yA_traj yB_traj yC_traj yD_traj) / 4; % 计算每个人到中心的距离 dist_A_to_center sqrt((xA_traj - center_x).^2 (yA_traj - center_y).^2); avg_distance mean([dist_A_to_center, ... % 计算平均距离 sqrt((xB_traj - center_x).^2 (yB_traj - center_y).^2), ... sqrt((xC_traj - center_x).^2 (yC_traj - center_y).^2), ... sqrt((xD_traj - center_x).^2 (yD_traj - center_y).^2)], 2); % 找到平均距离第一次小于阈值如0.1的时间索引 threshold 0.1; meet_index find(avg_distance threshold, 1, first); if ~isempty(meet_index) meet_time t(meet_index); fprintf(模拟中四人平均距离小于 %.2f 的时刻 t ≈ %.4f 秒\n, threshold, meet_time); fprintf(理论相遇时间 L/v %.2f 秒\n, L/v); end % 选取A的轨迹上的一些点计算切线与径向的夹角可选更复杂 % 需要数值微分求速度切线这里用差分近似 % dt diff(t); dx diff(xA_traj); dy diff(yA_traj); % vx_approx dx ./ dt; vy_approx dy ./ dt; % 取中点时间的位置向量... % 夹角计算略可自行尝试。4.2 模型变体与拓展思考经典模型跑通后我们可以尝试改变规则探索更多可能性这能极大锻炼建模思维。人数变化N人追逐将四人推广到N个人初始时均匀分布在一个正N边形的顶点上每个人追逐其顺时针方向的下一个人。数学模型完全类似只是微分方程组变得更大2N个方程。你可以尝试编写一个通用函数n_chase_odes.m输入参数为人数N和速度v。模拟一下五边形、六边形的情况轨迹会变得更加密集和有趣。当N很大时轨迹会趋近于一个圆吗速度不同让四个人的速度大小不同比如vA 1.0, vB 0.8, vC 1.2, vD 1.0。对称性被打破他们的轨迹将不再相同相遇点也不会是中心。最终他们会相遇于一点吗还是其中速度快的人会“追上”速度慢的人形成不同的相遇顺序这需要模拟来回答。修改ODE函数为每个人传入不同的速度参数即可。追逐规则变化滞后追逐每个人不是追逐目标的当前位置而是追逐目标在Δt时间之前的位置。这更符合一些现实情况如反应延迟。这需要保存历史位置数据模型会变成“延迟微分方程”DDEMATLAB中用dde23求解器处理。预测性追逐假设每个人能预测目标的运动并朝着预测的未来位置奔跑。这需要更复杂的模型可能涉及二阶导数的估计。加入随机扰动在速度方向上加入一个小的随机噪声模拟现实中的不确定性。这会将确定性ODE变成“随机微分方程”SDE。他们的轨迹还会汇聚吗可能会在一个小区域内随机游走。这可以用欧拉-丸山法等SDE数值方法模拟。三维空间追逐将场景扩展到三维空间比如一个正四面体的四个顶点。状态变量变成12个每个人的x,y,z坐标追逐方向的计算从二维平面扩展到三维空间向量但核心公式速度 v * (目标位置 - 自身位置) / 距离依然成立。可视化会从二维线图变成三维轨迹图更加炫酷。4.3 数值方法的深入探讨ode45的替代与精度控制我们使用了ode45它是非刚性问题的“万金油”。但对于某些变体模型如刚性系统或需要更高精度时了解其他求解器是有益的。ode23使用Bogacki-Shampine方法比ode45阶数低有时在精度要求不高时更快。ode113可变阶数的Adams-Bashforth-Moulton方法对于光滑问题且需要高精度时可能比ode45更高效。ode15s适用于刚性系统或当ode45很慢的情况。如果我们的模型加入了某些“硬”约束或极快的瞬态过程可以尝试用它。我们可以通过odeset来设置求解器的选项控制精度和输出options odeset(RelTol, 1e-6, AbsTol, 1e-9, Stats, on); [t, Y] ode45((t,Y) four_chase_odes(t, Y, v), tspan, Y0, options);RelTol相对误差容限默认1e-3。减小它如1e-6会提高精度但增加计算时间。AbsTol绝对误差容限默认1e-6。对于状态变量值可能很小的系统可能需要调整。Stats设为on会在求解结束后显示计算统计信息如函数调用次数有助于评估计算成本。经验之谈何时需要调整容差大多数时候默认设置就够了。但如果你发现模拟结果不满足物理直觉比如能量不守恒、轨迹明显不对称或者改变tspan终点时间结果差异很大那就可能需要收紧RelTol。对于这个追逐问题默认设置通常能给出非常平滑美观的轨迹。5. 从模拟到洞察将结果用于分析与报告完成模拟和可视化只是第一步如何从这些数据中提炼出有价值的结论并清晰地呈现出来是数学建模的最终目的。5.1 定量分析距离与时间的关系我们可以绘制每个人到中心点的距离随时间变化的曲线。这能直观展示他们是如何逐渐靠近的。% 计算并绘制距离-时间曲线 figure; hold on; grid on; plot(t, dist_A_to_center, r-, LineWidth, 1.5, DisplayName, A到中心距离); plot(t, sqrt((xB_traj - center_x).^2 (yB_traj - center_y).^2), g-, LineWidth, 1.5, DisplayName, B到中心距离); plot(t, sqrt((xC_traj - center_x).^2 (yC_traj - center_y).^2), b-, LineWidth, 1.5, DisplayName, C到中心距离); plot(t, sqrt((xD_traj - center_x).^2 (yD_traj - center_y).^2), m-, LineWidth, 1.5, DisplayName, D到中心距离); xlabel(时间 t); ylabel(到中心点的距离); title(四人到中心点距离随时间变化); legend(Location, best); hold off;你会发现四条曲线完全重合这再次验证了运动的对称性。曲线形状大致呈指数衰减趋势末期趋近于零但并非严格的指数函数。5.2 敏感性分析初始条件的影响经典的模型始于正方形顶点。如果初始位置不是完美的正方形呢比如让其中一个人稍微偏离顶点。系统还会汇聚于一点吗那一点还是中心吗通过微调初始条件Y0运行模拟观察轨迹和最终汇聚点的变化可以分析系统对初始条件的敏感性。这对于理解系统的稳定性很有帮助。一个健壮的模型或算法应对小的扰动不敏感。5.3 将项目打包创建可交互的App或函数为了让你的工作更容易分享和演示可以考虑用MATLAB的App Designer创建一个简单的图形用户界面GUI应用。你可以添加滑块来控制速度v和正方形边长L添加按钮来开始/暂停动画甚至添加下拉菜单来选择不同的人数N人追逐。这不仅能提升项目的完成度也是学习MATLAB GUI编程的好机会。即使不做成完整的App至少也应该将你的核心代码封装成一个整洁的函数例如function [t, Y, traj] simulate_four_chase(L, v, tmax) % 模拟四人追逐问题 % 输入 % L: 正方形边长 % v: 速度 % tmax: 最大模拟时间 % 输出 % t: 时间向量 % Y: 状态矩阵 % traj: 结构体包含每个人的轨迹 xA, yA, ... % ... 函数体包含之前的设置、求解和提取轨迹的代码 end5.4 在建模竞赛中的应用思考这类动力学模拟问题在数学建模竞赛中常有出现。它教会你的不仅仅是MATLAB操作更是一套完整的解决问题的方法论问题抽象将文字描述转化为微分方程。数值求解选择合适的算法如ODE求解器将连续问题离散化。编程实现编写稳健的代码处理边界情况如除零。结果可视化用静态图和动画直观展示过程。分析与验证通过对称性、极限情况等验证结果的合理性进行参数敏感性分析。模型拓展思考改变假设人数、速度、规则后模型的变化。当你面对一个诸如“无人机编队集结”、“动物群体迁徙路径模拟”等赛题时本次练习中掌握的“基于微分方程的智能体模拟”框架将是一个强有力的起点。你学会了如何定义个体的行为规则微分方程如何用数值方法集成所有个体的运动以及如何分析和解释涌现出的群体现象。