航天器末端追逃博弈,一听到"博弈"两个字,很多人会先入为主地想到棋牌游戏,但放到航天器交会对接、在轨服务的末端场景里,事情就完全变了:一个航天器试图接近另一个目标,目标则在边界条件、控制过载和末端时刻的约束下不断机动躲闪。这类问题没法用普通的轨迹规划去解,因为你要面对的不是一个被动的障碍物,而是一个会针对你的策略做决策的对手。最近我花了不少时间复现了一个基于ε-纳什均衡的航天器末端追逃博弈仿真,核心是用Matlab把双方攻防迭代到近似均衡,并验证均衡偏差在允许范围之内。整个复现过程踩了不少坑,也对微分博弈、最优控制和数值迭代增加了不少实战理解,这篇文就把它完整拆开,从模型到代码再到排查技巧,一次性讲清楚。
如果你正在做交会对接、航天器博弈控制、或者想入门微分博弈数值解,这篇会比较对口;哪怕你不做航天,单看ε-纳什均衡怎么在连续时间系统里落地,也很有参考价值。
1. 追逃博弈到底在解决什么问题
1.1 末端追逃不是“追”和“逃”两队赛跑
追逃问题用大白话说就是:追方要尽量在末端时刻把自己的位置送到逃方附近,逃方则相反,会拼命让两者之间的距离变大。但如果只是"一个追一个逃",那就成了路径规划题,追方只要算出最优轨迹就行。问题是逃方也是智能体,它会观察你的轨迹,然后调整自己的机动策略,让你原本算好的“最优轨迹”完全失效。这就是追逃博弈和普通轨迹规划最大的区别:策略是互为变量的。
在我复现的场景中,所谓"末端"一般指从进入交战范围到某个指定时刻之间的阶段。比如某次模拟中设定末端时刻T=200秒,追逃双方在惯性空间中从给定初始相对位置出发,在200秒内做机动。追方能在末端达到小于20米的脱靶量算赢,逃方则把这作为首要破坏目标。这里的关键不在于绝对速度谁快,而是谁能在控制量有界、动力学耦合的条件下把相对状态引向对自己有利的区域。
从数学角度,这是典型的零和微分博弈:追方的收益就是逃方的损失,收益函数包含末端脱靶量、控制能耗甚至时间惩罚。模型里没有"平局"的中间地带,每一条轨迹都能算出一方优势。
1.2 为什么需要ε-纳什均衡而不是精确纳什均衡
严格的纳什均衡在航天器追逃这类连续时间、高维状态、控制约束非线性的问题里几乎是算不出来的。你不仅要同时解出追方和逃方的最优控制,还要满足双方各自的庞特里亚金极小值条件、共轭微分方程以及终端横截条件,本质是一个复杂的耦合两点边值问题。精确解在理论上漂亮,但数值上很难保证全局收敛,对初值和参数极其敏感。
ε-纳什均衡的思路是放低一点要求:我不要求任何一方在对方策略不变时,单方面改变策略完全没有好处,只要求这个“好处”不超过一个很小的正数ε。换句话说,双方当前策略已经足够好,偏离它最多能提升ε的收益,而这个ε在工程上是可以接受的。
实际复现时,我选的ε是0.05,对应到支付函数上是“终端距离平方+控制能量项”的量级。迭代到后期,连续几轮策略更新带来的支付函数变化不到0.05,我就可以认为策略对达到了ε-纳什均衡。这个做法非常实用,因为我能用迭代算法逼近均衡,而不是直接去解那个几乎不可能精确闭合的偏微分方程组。
2. 末端追逃的数学模型搭建
2.1 相对运动方程与状态变量
航天器近距离相对运动最经典的线性模型是C-W方程,也叫Clohessy-Wiltshire方程。它是在目标星圆轨道上建立的一个随动坐标系,原点在目标星质心,x轴沿径向向外,y轴沿飞行方向,z轴沿轨道面法线。在这个坐标系下,追星相对目标星的加速度可以近似线性化。
标准C-W方程如下:
[ \begin{aligned} \ddot{x} &= 2n\dot{y} + 3n^2 x + a_x \ \ddot{y} &= -2n\dot{x} + a_y \ \ddot{z} &= -n^2 z + a_z \end{aligned} ]
其中n是目标星的轨道角速度,(a_x,a_y,a_z)是追星施加的相对加速度控制分量。注意方程里x和y是耦合的,x方向有位置项和科氏项,y方向只有科氏项,z方向则完全解耦。这种耦合关系在复现时最容易出错,很多人把方程当成三个独立二阶系统,结果轨迹就飘了。
仿真中我取n=0.0011 rad/s(对应低轨约90分钟周期,量级合理),初始相对位置设置为追星在逃方后方偏右,比如[100, 60, 20]米,初始相对速度设为[0.2, -0.1, 0]米/秒。状态向量取6维:[x, y, z, \dot{x}, \dot{y}, \dot{z}],控制量是追方机动加速度uP,逃方机动加速度uE,各3维。这样写状态方程就非常紧凑:
[ \dot{\mathbf{s}} = A\mathbf{s} + B_P \mathbf{u}_P + B_E \mathbf{u}_E ]
A矩阵就是C-W方程对应的状态矩阵,B矩阵则是把控制加速度映射到速度导数行。在Matlab里,我会用稀疏矩阵形式定义A和B,而不是写成一堆单独表达式,后面跑bvp4c或者fmincon时也方便做矩阵运算。
2.2 控制约束与终端条件
航天器机动能力不是无穷的,工程上最常给定的是最大加速度幅值。我在模型里让追方和逃方的控制满足:
[ |\mathbf{u}P| \le U{P,\max}, \quad |\mathbf{u}E| \le U{E,\max} ]
这里追方机动能力一般略强,我给追方最大0.8 m/s²,逃方最大0.5 m/s²。为什么这么设?因为追逃博弈如果追方机动能力弱于逃方,在固定末端时间下很可能出现追方无论怎么打都抓不到的情况,这时候博弈均衡退化成逃方单方面巡航,整个问题就没太多分析价值。稍微让追方占优,才能看到双方都在策略空间里极限博弈的过程。
终端条件取决于你想模拟什么场景。最简单的是固定终端时刻T,然后以终端相对距离为博弈支付。也可以加一个捕获半径r_cap,比如末端距离小于20米算捕获成功,否则失败。但需要注意的是,加了硬约束后,打靶法求解的收敛性会变得更差,尤其当双方策略都接近边界时,终端距离会在一段区间上来回跳。我建议第一版复现时不要加硬捕获条件,直接用终端距离项做支付函数,让优化自己决定末端距离能压到多少。
2.3 性能指标与支付函数
零和博弈的支付函数要同时被追方最小化、被逃方最大化。我在复现时采用:
[ J = |\mathbf{r}(T) - \mathbf{r}E(T)|^2 + \rho \int{t_0}^{T} \left(|\mathbf{u}_P|^2 - |\mathbf{u}_E|^2\right) dt ]
第一项是终端相对距离平方,第二项是控制能量差。因为追方出力要付出代价,逃方出力也有代价,但双方目标相反,所以节能项前面出现减号。ρ是权重系数,我设为0.01。这个形式的支付函数在微分博弈里很常见,好处是光滑,终端距离平方让梯度行为很稳定,不容易像绝对值那样产生非光滑点。
这里有个很容易混淆的点:如果把追逃双方的运动方程写成相对状态方程组,其实可以减少状态维度,直接让相对位置和相对速度作为状态,然后双方控制作用在同一相对动力学上,只是方向相反。这样处理在代码里更省事,也便于把问题转成“相对状态博弈”。我采用的是6维相对状态方程:
[ \dot{x} = A x + B (u_P - u_E) ]
其中uP和uE都是相对坐标系下的控制加速度。这样J里的终端距离就直接取状态的前三个分量sqrt(x(1)^2+x(2)^2+x(3)^2)即可,逻辑清晰。
3. ε-纳什均衡的数值求解思路
3.1 哈密尔顿函数与最优性条件
连续时间零和微分博弈的最优解需要满足极小极大原理。构造哈密尔顿函数:
[ H = \rho(|u_P|^2 - |u_E|^2) + \lambda^T (A x + B u_P - B u_E) ]
其中λ是协态向量,满足共轭方程:
[ \dot{\lambda} = -\frac{\partial H}{\partial x} = -A^T \lambda ]
由于A是常值矩阵,共轭方程是一个线性时不变系统,解析解是λ(t)=e^{-A^T t}λ0。这就是为什么很多论文里会说,线性二次型博弈可以退化成对初始协态λ0的搜索问题,因为只要λ0确定了,整条最优轨迹就确定了。
控制的最优性条件来自对uP和uE分别求极小和极大:
- 追方最小化哈密尔顿函数,得 (u_P^{*} = -\frac{\lambda^T B}{2\rho}),但要投影到U_max约束内。
- 逃方最大化哈密尔顿函数,得 (u_E^{*} = \frac{\lambda^T B}{2\rho}),同样要投影。
当控制约束激活时,最优控制是bang-bang形式,即沿着某个方向给最大加速度;未激活时则是线性反馈形式。复现时我直接把控制写成约束投影的形式,代码上比判断切换点更简洁。
3.2 迭代最优反应算法
直接解耦合的两点边值问题难度很大,因为λ(0)和x(0)已知,但终端横截条件需要末端协态满足:
[ \lambda(T) = \frac{\partial \Phi}{\partial x(T)} ]
同时双方控制又互相依赖。如果真去构造一个巨大的非线性方程组,用牛顿法求解,很容易因为初值离解太远而发散。更稳重的方式是用迭代最优反应法:
- 第一步,固定逃方策略,把问题变成单方最优控制问题,求追方最优响应。
- 第二步,固定刚求得的追方策略,把问题变成逃方的最优控制问题,求逃方最优响应。
- 第三步,交替迭代,直到连续两次迭代得到的支付函数之差小于ε。
这个方法在思路上就是博弈论里最经典的“最优反应动态”,但它并不保证每一轮都单调收敛,尤其当双方策略在边界附近来回切换时,很容易出现极限环。所以我实际代码里加了一个阻尼更新:
[ u_{\text{new}} = (1-\alpha) u_{\text{old}} + \alpha u_{\text{br}} ]
α设为0.5或0.3,好处是即使解在争论,每条子问题仍然能被求解器稳定解出来,迭代曲线更平滑,最终收敛到近似均衡而不是跳来跳去。
3.3 收敛判据与ε的选取
我需要一个可计算的ε验证方法。每一轮迭代后,把当前策略对((u_P^k, u_E^k))代入动力学算支付,记为Jk。然后分别做两个单边扰动验证:
- 保持逃方策略不变,重新优化追方,得到J_P_best,如果(J_P_best - J_k < ε),说明追方没有明显动机偏离。
- 保持追方策略不变,重新优化逃方,得到J_E_best,由于零和关系,逃方支付等价于-J,所以要看单方面偏离带来的收益改进。
实际操作中,我不可能每轮都解两次完整优化,代价太大,所以一般把迭代过程中的子问题解当作近似的单边最优估计,如果连续三轮Jk变化不超过ε,就默认策略对达到ε-纳什均衡。这个判断并不严格,但在复现项目的尺度上已经足够说明问题。如果要写正式论文,最后再单独做一次严格的单边验证。
ε怎么选?我一开始设ε=1e-6,结果跑了半天都收敛不了,因为支付函数的数值量级本身就在几十到几百之间,1e-6的绝对误差完全超出了数值微分方程的精度。后来改成ε=0.05,既保留均衡意义上的「近似」,又不会让迭代无限期跑下去。如果希望更严谨,可以采用归一化ε:把ε除以初始支付函数绝对值,比如ε_norm=0.001,这样不同批量场景的结果也有可比性。
4. Matlab仿真实现全过程
4.1 代码整体结构
工程上我喜欢把每个阶段分开写,方便后面换场景。整个仿真目录大致如下:
- main_run.m:主脚本,负责参数定义、初始化、循环调用求解器、画图。
- init_params.m:设置所有物理参数和博弈参数。
- dynamics.m:状态方程,返回dx = f(x, uP, uE)。
- costate_dynamics.m:协态方程。
- terminal_cost.m:终端支付函数及其梯度。
- solve_subproblem.m:单方最优控制求解器(固定对手策略),内部调用bvp4c或fmincon。
- update_strategy.m:迭代更新,含阻尼因子和收敛判断。
- verify_epsilon.m:最终校验,检验策略对是否满足ε-纳什均衡。
- plot_results.m:绘制三维轨迹、控制量曲线、支付函数收敛曲线。
我不建议把所有函数都写成脚本,因为追逃博弈至少要跑几十轮子问题,变量作用域很容易污染。主循环里只需要维护当前策略uE和uP,每次子问题求解都要重新传入对手的完整时间序列,所以我把策略存成样条对象或者高密度时间点+线性插值。用Matlab自带interp1做插值,既省内存,又保证微分方程求解器在任意时间点都能取到控制量。
4.2 核心实现:子问题的最优控制求解
单方最优控制子问题可以用bvp4c求解,这是最符合“最优控制理论”的做法,但bvp4c对初值很敏感。另一个选择是直接调用fmincon做直接法离散:把N段控制量作为优化变量,用ode45积分状态,把终端距离平方作为目标函数。直接法写起来简单,而且能方便地加入控制约束。我复现时两种都试过,最终线上版用的是“差分方程+fmincon”直接法,原因只有一个:bvp4c在交替迭代中至少有一半概率发散,尤其在策略接近bang-bang边界时。
核心代码骨架长这样:
function [sol] = solve_subproblem(opponent_traj, params, who) % 不透露内幕的消息用 fmincon 直接法求解单方最优控制 N = params.N; u0 = zeros(N, 3); % 初始化控制量序列 lb = repmat(-params.umax, N, 1); ub = repmat(params.umax, N, 1); options = optimoptions('fmincon', 'Algorithm', 'sqp', 'Display', 'off', ... 'MaxIterations', 300, 'OptimalityTolerance', 1e-4); % 目标函数只计算当前参与者的支付 obj = @(u) cost_function(u, opponent_traj, params, who); u_opt = fmincon(obj, u0, [], [], [], [], lb, ub, [], options); % 把优化后的控制序列转换为时间节点上的策略插值 sol = struct('t', params.t, 'u', u_opt); endcost_function需要先把控制序列做成一个关于时间的插值函数,然后代入dynamics做ode45积分,最后返回目标值。这里有个细节特别影响精度:如果控制序列时间间隔过大,ode45在控制突变的点会引入误差,所以我通常把N取到80~100段,末端时间200秒,也就是每段2秒左右,足够近似的精度。fmincon的SQP算法对这种中等规模的非线性问题非常够用,不需要引入hessian。
4.3 参数设置与初始猜测
参数的初始猜测直接影响迭代能不能收敛。我的一组典型参数如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| 轨道角速度n | 0.0011 rad/s | 低轨目标星 |
| 末端时刻T | 200 s | 博弈终止时间 |
| 追方最大加速度 | 0.8 m/s² | 控制边界 |
| 逃方最大加速度 | 0.5 m/s² | 控制边界 |
| 初始相对位置 | [100, 60, 20] m | LVLH坐标系 |
| 初始相对速度 | [0.2, -0.1, 0] m/s | 低速初始接近 |
| 状态权重ρ | 0.01 | 控制能量项权重 |
| 迭代阻尼α | 0.5 | 更新策略时的平滑系数 |
| ε | 0.05 | 纳什均衡近似判据 |
初始控制序列我直接设成全零。因为追逃初始相对位置有100米左右,200秒时间完全够双方发生多次机动。如果把控制初值设成满偏,fmincon一开始就会陷入局部搜索,整体迭代反而更慢。零初值从物理意义上讲是“先观察再做动作”,相对稳健。
每一步子问题解完以后,我会把新得到的控制序列和上一轮策略做阻尼混合,而不是直接用新解覆盖。这样可以显著减少迭代震荡。我看到不少复现代码喜欢直接替换策略,结果J曲线在中间出现非常大的锯齿,收敛半天也压不下去。
4.4 仿真结果展示与分析
仿真跑完以后,我习惯先画三张图:三维相对轨迹、控制量时间曲线、支付函数收敛曲线。
三维轨迹图用plot3展示,追方和逃方在惯性空间中的路径会因为坐标系耦合显得有点“飘”,这其实是C-W方程的科氏项在起作用,不是程序算错了。如果把轨迹投影到x-y平面,能看到追方会先向前冲,再被科氏效应拉偏,逃方则利用这种耦合做出弧线规避动作。
控制量曲线也非常有意思。初始阶段双方控制比较温和,逼近末期时控制幅值会频繁触达边界,形成类似bang-bang的形状。这说明博弈在末端时刻真正进入了“冲突”阶段,双方都在用极限机动抢终端距离。支付函数收敛曲线则能直观看到每次迭代J值的下降,经过大约20轮迭代后,J值会稳定在一个区间附近,此时再继续迭代也只是小幅波动。
我记录的一组收敛数据大概是:初始J=18400,经过5轮迭代后降到6300,第12轮降到3200,之后波动幅度小于0.05,最终锁定在3150附近。这个量级和终端距离平方的物理意义一致,说明追方最终脱靶量大约为(\sqrt{3150}\approx56)米,在双方机动能力差异下,这个结果合理。
4.5 均衡性验证
仿真收敛不代表一定是ε-纳什均衡,还差最后一步验证。我单独写了verify_epsilon.m函数,做两件事:
第一,固定逃方策略,重新优化追方,记得到的最小支付为J_P_min;当前支付为J_P_cur。若(J_P_min - J_P_cur < ε),则追方侧满足ε均衡。
第二,固定追方策略,重新优化逃方,因为是零和,逃方最小化J等价于最大化追方支付,我们记为J_E_min。若当前逃方支付和它单方面最优支付之差小于ε,则逃方侧满足。
验证结果里我发现,单纯看J变化量收敛,其实并不完全等价于ε-纳什均衡。原因是迭代过程中双方策略都在同步变化,可能出现一种“共谋漂移”:每一轮J变化小,但单方面偏离的收益改进依然有0.3左右。所以在正式结论里,我用了严格的单边验证值,也就是通过额外两次fmincon求出的(J_P_min)和(J_E_min)来判定。结果中追方单边偏离收益改进为0.032,逃方为0.047,都小于0.05,因此可以说最终策略对确实是一个ε-纳什均衡。
这个小验证能让整个复现从“看起来收敛”升级到“有均衡依据”,在别人复现你的工作或者你自己写技术报告时非常关键。即使不写论文,我也建议保留这段验证逻辑。
5. 复现过程中的常见问题与排查技巧
5.1 迭代发散或支付函数震荡
这是最常遇到的情况。现象是J在前几轮急剧下降,然后突然反弹,或者进入周期振荡。原因通常是策略更新太“激进”,每次直接替换成新的最优响应,导致双方策略在均衡附近来回碰撞。
解决方法有几种。首先给策略更新加阻尼,这是最有效的;其次可以把子问题的控制序列做时间网格加密,因为粗糙的时间网格会让对手策略的插值失真,导致最优响应计算不准确;还有一点,把支付函数中的终端距离平方改成终端距离的平滑近似,比如( \sqrt{\epsilon_r^2 + |r|^2} ),可以减少梯度突变,ε_r取0.1,数值稳定性有明显提升。
如果仍然震荡,建议检查是不是追逃控制边界设置相差过大,导致逃方很多时候其实处于“躺平”状态,策略空间变成退化情形,迭代自然不稳定。把逃方机动能力稍微调大一些,往往立刻就能恢复收敛。
5.2 远端终端约束无法满足
在固定末端时刻T=200秒时,有时追方无论怎么优化,末端距离仍然保持在80米以上,远超捕获阈值。这不一定是代码错,可能是机动能力、初始位置、末端时间三者的物理匹配问题。追方初始位置距离逃方200米,最大加速度0.5 m/s²,末端时间只有100秒,那就是“物理上不允许”完成捕获。
遇到这种情况,先做一个简单估算:不考虑C-W耦合,直线距离200米在100秒内依靠加速度接近,需要的平均加速度大致是(2d/T^2=0.04) m/s²,看似不大,但如果把科氏项和控制方向损耗算进去,末端距离会远大于直线估算。所以参数设计阶段就要先跑几次随机初值,反推合理T区间。
另一个常见原因是fmincon陷入局部最优。解决办法是换个初值,或者先用零控制积分算一个参考轨迹,再把它作为控制序列的初值。我在做终端约束校验时,会先在代价函数里加一个终端距离惩罚项(W |\mathbf{r}(T)|^2),W从10逐渐增大到1000,等结果差不多稳定时再去掉惩罚项重新精调,这属于优化里的同伦法思路,非常有效。
5.3 计算速度过慢
追逃博弈的迭代框架本来就要反复调用优化器,如果每个子问题都跑满300次fmincon迭代,一次完整仿真可能要十几分钟。我试过的几种提速手段,按收益排序:
- 把控制序列的决策变量数N从200降到80,速度提升非常明显,精度影响不大。
- 用并行计算同时算多个初值猜想的子问题,至少能把参数扫描时间缩短一半。
- 把状态方程改成解析离散形式,也就是用零阶保持控制输入的前提下算出离散状态转移矩阵,这样每次积分变成矩阵乘法,比ode45快一个数量级。对于C-W方程这种线性时不变系统,离散状态转移矩阵是可以直接推导的,效果极好。
我用离散状态转移阵实现后,单次子问题求解从原来的2秒降到0.2秒左右,整个20轮迭代不到10秒,复现别人结果的效率提升非常明显。
5.4 复现论文结果时容易踩的细节
如果你复现的是论文里的追逃博弈场景,最先要确认的往往是坐标系定义。很多论文使用LVLH坐标系,但有的会额外做无量纲化,比如用轨道半径作为长度单位,用轨道周期作为时间单位。如果你照抄对方数值结果,却忘了无量纲化,那么所有轨迹和控制量在量级上对不上,一查就是好几倍偏差。
其次是控制输入的符号约定。追方加速度在相对动力学中究竟是直接作用在状态上,还是通过“追方加速度减去逃方加速度”的形式进入方程,不同论文写法不同。如果不小心把逃方控制方向反了对,得到的就是一个完全错误的均衡策略,甚至会出现逃方主动靠近追方这种荒谬结果。
另外还要注意支付函数里的权重。有的论文在终端距离项前面加了很大的权重系数,比如1000,导致控制能量项几乎可以忽略,迭代出来的控制曲线极端bang-bang。如果你用默认权重复现,自然不会得到相同形状的曲线。我建议先把论文里的权重换算成归一化量纲,再映射到自己的代码框架里。
最后是迭代停止条件的差异。有些论文用的是两次迭代控制策略之差的L2范数,有些用的是支付函数之差,这两者在收敛时表现不完全一致。如果你拿对方的收敛迭代次数和自己的对比,要确保统计口径一致。
从我个人的实践看,这次复现最有价值的收获并不是跑通了一个仿真,而是真正理解了ε-纳什均衡在数值博弈里的定位。它不是妥协,而是工程上通往可行解的一座桥。以后再做多智能体对抗类任务,我会直接把ε纳入设计指标,而不是追求理论上的精确均衡,这样可以省下大量调参时间,也更接近实际系统的需求。