1. 为什么讲完变分法还要单独补一节哈密尔顿函数
先说结论:哈密尔顿函数法是整个最优控制理论里最值得反复抄写的一页笔记。最近在整理“最优控制理论”系列,二+这一讲本来是上一讲变分法的补充,结果越写越觉得它才真正的主线。很多人学到这里会有个困惑:上一讲用变分法求泛函极值,已经推导出欧拉-拉格朗日方程了,为什么还要重新定义一个哈密尔顿函数?甚至有些教材把它放在庞特里亚金极大值原理里一闪而过,导致初学者觉得这只是一个数学技巧。
实际上,哈密尔顿函数法不只是“技巧”,它是把带状态方程约束的泛函极值问题,转化成一族常微分方程两点边值问题的标准框架。你后面会遇到的所有东西——动态规划、LQR、模型预测控制里的终端约束、燃料最优控制里的开关结构和奇异弧——最后都能从这一页笔记里长出来。这篇补充分享,适合刚学过变分法但对哈密尔顿函数法还不够熟的读者,也适合那些已经会列公式、但一遇到横截条件或数值求解就翻车的同学。
1.1 拉格朗日乘子的“升级版”
先回到最一般的问题。假设要最小化
[ J=\phi(x(t_f),t_f)+\int_{t_0}^{t_f} L(t,x,u),dt ]
同时系统满足状态方程
[ \dot x=f(t,x,u),\quad x(t_0)=x_0 ]
如果只是普通函数约束极值,我们很自然会引入拉格朗日乘子。但这里约束是一条随时间变化的微分方程,所以乘子不再是一个常数,而是一个随时间变化的函数 (\lambda(t))。把这个想法写进增广泛函:
[ J_a=\phi(x(t_f),t_f)+\int_{t_0}^{t_f}\left[L(t,x,u)+\lambda^T(t)\left(f(t,x,u)-\dot x\right)\right]dt ]
然后定义哈密尔顿函数:
[ H(t,x,u,\lambda)=L(t,x,u)+\lambda^T(t)f(t,x,u) ]
为什么说这是升级版?因为原来的问题是在满足状态方程的前提下找极值,现在把约束“装”进了被积函数,变成了一个在全部时间点上对状态、控制、协状态同时求极值的问题。这里 (\lambda(t)) 的物理含义也很有用:它表示“如果当前状态发生微小变化,最优代价会怎么变”,也就是最优代价函数对状态的梯度。沿着最优轨迹,它时刻反映了状态的边际价值。
1.2 哈密尔顿函数把问题拆成了“两层”
很多教材直接扔出三个必要条件,然后开始刷题。但我想先讲清楚一个更直观的理解:哈密尔顿函数法把原来的优化问题拆成了两层。
第一层是“微观层”,在任意时刻 (t),当状态 (x(t)) 和协状态 (\lambda(t)) 已知时,我们要选择一个控制 (u(t)),让哈密尔顿函数在当前时刻取最小值(对于最小化问题)。这就是最优控制所满足的瞬时最优性条件。
第二层是“宏观层”,状态和协状态不是随便选,它们要满足一组常微分方程,并且两端还要满足边界条件。状态方程描述系统物理演化,协状态方程描述代价的边际信息如何随时间传播。
这两层合在一起,才构成完整的最优性必要条件。你可能会问:为什么还要第二层?因为如果只做瞬时优化,我们根本不知道选哪个 (u) 才是整体最优。比如开车从A点到B点,每一时刻都猛踩油门可能让当前速度最快,但整体未必最早到达。协状态方程就是用来把这些局部决策“串”成全局最优的。
2. 正则方程与横截条件:先把最容易记混的边界条件钉死
进入实操之前,必须把哈密尔顿函数法的三个必要条件和边界条件理清楚。很多同学能默写出状态方程,但一写到协状态方程的负号,就要翻书;一遇到终端状态自由,就不知道 (\lambda(t_f)) 到底等于几。这一节我们把整套框架钉死。
2.1 正则方程:状态与协状态的对称结构
对增广泛函取一阶变分,令变分为零,可以得到最优解必须满足的必要条件:
[ \dot x=\frac{\partial H}{\partial \lambda}=f(t,x,u) ]
[ \dot \lambda=-\frac{\partial H}{\partial x} ]
[ \frac{\partial H}{\partial u}=0 ]
第一条就是状态方程,第二条是协状态方程,第三条是控制方程。当控制没有约束、并且哈密尔顿函数对 (u) 光滑时,第三条就是 (\partial H/\partial u=0)。如果控制有界,第三条要改写成更一般的形式,这个我们下一节说。
一个容易被忽略的细节是:协状态方程为什么带负号?直观理解是,协状态度量的是“状态变化带来的代价敏感性”,代价越往后越清楚,所以这个敏感性的时间演化方向天然与系统状态方程相反。不可避免的后果是,状态方程从初始条件向前推,协状态方程通常要从终端条件向后推,问题变成典型的两点边值问题。这也是为什么最优控制很少能像仿真那样直接一步步向前算。
如果哈密尔顿函数不显含时间 (t),并且系统是时不变的,那么沿着最优轨迹有
[ \frac{dH}{dt}=0 ]
也就是说 (H) 是最优轨迹上的常数。这个性质在做最短时间控制、计算自由终端时间时特别有用,后面会看到。
2.2 横截条件:终端的“边界补缺”
如果状态初始值给定,一般不需要额外条件。问题集中在终端。不同终端情况对应不同的横截条件,这一步错了,整个数值解就废了。
最常见的情况可以整理成一张表:
| 终端条件类型 | 横截条件 |
|---|---|
| (t_f) 固定,(x(t_f)) 给定 | 不需要 (\lambda(t_f)) 的横截条件,直接用 (x(t_f)=x_f) |
| (t_f) 固定,(x(t_f)) 自由,无终端代价 | (\lambda(t_f)=0) |
| (t_f) 固定,(x(t_f)) 自由,有终端代价 (\phi(x(t_f))) | (\lambda(t_f)=\dfrac{\partial \phi}{\partial x(t_f)}) |
| (t_f) 固定,(x(t_f)) 满足等式约束 (\psi(x(t_f))=0) | (\lambda(t_f)=\dfrac{\partial \phi}{\partial x(t_f)}+\nu^T\dfrac{\partial \psi}{\partial x(t_f)}),并满足约束本身 |
| (t_f) 自由,其他条件不变 | 增加一个标量条件:(H(t_f)+\dfrac{\partial \Phi}{\partial t_f}=0),其中 (\Phi=\phi+\nu^T\psi) |
注意,上表中 (\nu) 是终端约束对应的拉格朗日乘子。如果问题没有终端代价但 (x(t_f)) 固定,那么自由终端时间条件下就是 (H(t_f)=0)。这个条件经常被漏掉,漏掉之后自由时间问题会变成欠定问题,怎么调初值都收敛不了。
还需要提醒一下:如果 (x(t_0)) 本身是自由的,初始端也会有类似条件,比如 (\lambda(t_0)=-\partial \phi_0/\partial x(t_0))。实际工程里初始状态大多给定,但做某些估计问题或路径规划时,会遇到两端状态都不完全确定的情况,那时要记得把两端的横截条件都列出来。
注意:不同教材对哈密尔顿函数的定义符号不同。如果看到协状态方程写成 (\dot\lambda=+\partial H/\partial x),先检查对方是不是把哈密尔顿函数定义成了 (L-\lambda^T f),或者把最优条件写成了极大值形式。符号错一个,后面所有量都会跟着错。
3. 控制约束和状态约束来了,别再用 (\partial H/\partial u=0) 硬刚
上一节我们讨论的是控制不受限制的“光滑”问题。但实际工程里控制几乎都有界,比如电机输出有限、阀门开度有限、机器人关节力矩有限。这时如果还强行用 (\partial H/\partial u=0),很可能得到一组不满足约束的虚假最优解。
3.1 控制有界:从梯度为零变成哈密尔顿函数取最小值
庞特里亚金极大值原理(其实对最小化问题应叫极小值原理)给出了一般形式:
[ u^*(t)=\arg\min_{u\in U} H(t,x,\lambda,u) ]
也就是说,在每个时刻,控制量要在可行集 (U) 内让哈密尔顿函数取最小值。当 (U) 是开集、内部光滑时,极小值点满足梯度为零,退化为 (\partial H/\partial u=0)。当 (U) 是闭区间,或者哈密尔顿函数关于 (u) 是线性函数时,最优控制往往落在边界上。
以最小时间控制为例,典型性能指标是
[ J=\int_{t_0}^{t_f}1,dt ]
这时哈密尔顿函数为
[ H=1+\lambda^T f(t,x,u) ]
如果控制 (u) 只以线性形式进入 (f),那么 (H) 关于 (u) 是线性的。让 (H) 最小,自然要把 (u) 推到可行域的一端。这就是“砰-砰控制”的来源。很多人第一次看到 bang-bang 控制觉得反直觉:为什么最优控制不是平缓调节,而是不停切换?因为性能指标没有惩罚控制量大小,自然会把控制推到极限。
如果切换函数在一段时间内恒等于零,情况会变得麻烦。此时一阶条件无法确定控制,最优轨迹可能进入“奇异弧”。这种情况在燃料最优控制里很常见:控制量既有界,又出现在性能指标中被惩罚,所以最优解往往包含“全力段—滑行段—全力段”的组合。判断奇异弧是否最优,需要用到广义勒让德-克莱布施条件,这已经超出基础课范围。但至少要知道:遇到切换函数持续为零时,别硬猜,先考虑奇异弧是否存在。
3.2 状态约束与内部点约束:协状态会“跳”
状态约束比控制约束难得多。原因很简单:控制约束只影响当前时刻的控制量,状态约束却会影响未来一段时间的轨迹。比如机器人末端不能碰到障碍物,这会让轨迹在接触约束边界前后出现分段。
处理状态约束的标准思路是在约束边界上把问题分成若干段,每一段单独用哈密尔顿函数法求解,再在拼接点上满足“连续性”条件。如果中间存在内部点约束,比如轨迹必须在某时刻经过指定位置,那么协状态在内部点处会发生跳跃:
[ \lambda(t_i^+)=\lambda(t_i^-)+\nu^T\frac{\partial q}{\partial x(t_i)} ]
其中 (q(x(t_i),t_i)=0) 是内部点约束。这个公式的本质和有限维约束优化里的拉格朗日乘子完全一样,只是作用在协状态的跨段跳跃上。
我的建议是:如果刚接触最优控制,先不要死磕状态约束的解析求解。工程上最稳的做法是先用直接法或数值优化软件得到一条候选轨迹,观察它是否触碰了约束边界,然后再回到间接法,用哈密尔顿函数法验证并精化。直接从数学上硬解状态约束,很容易被分段边界条件淹没。
4. 从哈密尔顿函数到LQR:同样是H,为什么最后解出的是Riccati方程
很多讲哈密尔顿函数法的课程,会突然跳到线性二次型调节器问题。学生一脸懵:刚才还在讲一阶必要条件,怎么下一秒就出现了黎卡提方程?这里我建议把连接过程看清楚,它其实是一次非常漂亮的“降维”。
4.1 线性二次型问题里为什么突然冒出来一个 (P(t))
考虑标准LQR问题:
[ \min J=\frac{1}{2}x^T(t_f)S_f x(t_f)+\frac{1}{2}\int_{t_0}^{t_f}\left(x^TQx+u^TRu\right)dt ]
系统为线性时不变:
[ \dot x=Ax+Bu ]
哈密尔顿函数为
[ H=\frac{1}{2}x^TQx+\frac{1}{2}u^TRu+\lambda^T(Ax+Bu) ]
由于控制无约束,控制方程为
[ \frac{\partial H}{\partial u}=Ru+B^T\lambda=0 ]
解得
[ u=-R^{-1}B^T\lambda ]
协状态方程为
[ \dot\lambda=-\frac{\partial H}{\partial x}=-Qx-A^T\lambda ]
到这里还看不出黎卡提方程的影子。关键一步是猜:既然问题是线性的,最优代价函数应当是状态 (x) 的二次型,那么协状态与状态之间应该存在线性关系:
[ \lambda(t)=P(t)x(t) ]
把这个关系代入状态方程和协状态方程,经过整理就得到:
[ -\dot P=A^TP+PA-PBR^{-1}B^TP+Q ]
这就是微分黎卡提方程。终端条件由终端代价给出:
[ P(t_f)=S_f ]
所以真实求解方向是从 (t_f) 向 (t_0) 反向积分,而不是从初始时刻向前积分。初次自己推导一遍这个关系,比背十遍“LQR就是解Riccati方程”要有用得多。
作为验证,看一个最简单的标量系统:
[ \dot x=u,\quad \min \frac{1}{2}\int_{0}^{T}\left(qx^2+ru^2\right)dt ]
这时黎卡提方程退化为
[ \dot p=\frac{p^2}{r}-q ]
如果时间足够长,稳态解满足
[ p=\sqrt{qr} ]
最优反馈控制为
[ u=-\frac{1}{r}px=-\sqrt{\frac{q}{r}}x ]
这个结果和经典LQR完全一致,但它是从哈密尔顿函数法推出来的。这就是为什么我总说:哈密尔顿函数法并没有在LQR里消失,它只是在一个具体的二次型结构里变成了P(t)。
4.2 协状态的另一种解读
如果从动态规划角度再看,会得到一个更漂亮的解释:协状态等于最优值函数对状态的梯度:
[ \lambda(t)=\frac{\partial V(t,x)}{\partial x} ]
其中 (V(t,x)) 是从当前状态 (x(t)) 出发到终点的最优剩余代价。在LQR里,因为值函数是二次型,所以这个梯度就是 (P(t)x)。在一般非线性问题里,这个关系也成立,虽然我们无法显式写出 (V(t,x)),但 (\lambda(t)) 始终携带“当前状态的边际代价”信息。
理解这一层有什么实际意义?它帮你在调初始协状态时建立直觉。比如终端代价越大,终端附近的 (|\lambda(t)|) 通常也越大;如果最优轨迹需要强烈避开某个区域,那么 (\lambda(t)) 会在进入该区域前出现明显变化。打靶法猜初值时,这种定性判断能省很多时间。
5. 数值求解时最容易翻车的四个细节
理论上,哈密尔顿函数法把最优控制问题化成了两点边值问题。可惜大多数真实系统不能解析求解,必须上数值方法。这里我根据自己的经验,把最长踩的坑按优先级列一下。
5.1 间接法数值求解:从猜初始协状态开始
间接法打靶的流程很直接:
- 写出哈密尔顿函数,求出最优控制关于状态和协状态的表达式。
- 把控制表达式代回状态方程和协状态方程,形成一个关于 ((x,\lambda)) 的增广常微分方程组。
- 猜测缺失的初始边界条件,通常是 (\lambda(t_0));如果终时自由,还要猜 (t_f)。
- 从 (t_0) 正向积分到 (t_f),检查终端横截条件是否满足。
- 不满足就用牛顿法或拟牛顿法更新猜测值,继续迭代。
这套方法看起来简单,实际用起来很考验初始猜测。尤其当系统具有不稳定模态时,状态、协状态会在积分末端指数爆炸,稍微偏差一点就面目全非。更稳的选择是多重打靶:把时间区间分成若干段,每一段都从段首积分,并引入连续的接续条件。这样能显著降低对初始猜测的敏感度。现在的边界值问题求解器,比如 SciPy 的 solve_bvp,本质上就是这类思路,适合解决中小规模问题。
5.2 四个非常具体的踩坑点
第一个坑:符号混乱。哈密尔顿函数定义、协状态方程符号、控制方程符号,三者必须保持一致。我见过太多人从不同教材各抄一半公式,最后解出来的“最优控制”其实是最大化哈密尔顿函数,而不是最小化。建议每个新问题都在草稿纸上从头推一遍,不要直接套记忆。
第二个坑:横截条件漏项。尤其是自由终端时间,很多人记得 (H(t_f)=0),但忘记如果有终端代价 (\phi) 还要加上 (\partial \phi/\partial t_f);或者终端约束里带着乘子 (\nu),忘了把 (\nu^T\psi_t) 加入终端哈密尔顿条件。这个坑的表现很迷惑:目标函数看起来在下降,但每个迭代点的“最优性”残差都压不下去。
第三个坑:量纲和尺度没有归一化。状态可能是0.01量级,协状态可能是上万量级,终端时间可能是几十秒。如果不做尺度变换,牛顿法的雅可比矩阵会病态,收敛非常慢。比较实用的做法是把时间归一化到 ([0,1]),把状态和控制也缩放到接近1的量级,然后再求解。
第四个坑:强制用 (\partial H/\partial u=0) 处理有界控制。只要控制进入饱和区域,梯度条件就不成立,需要改用庞特里亚金极小值条件,或者引入开关函数检测边界切换点。数值积分时,如果检测到开关函数过零,要精确找到过零点,而不是在粗网格上强行近似,否则轨迹会在开关点附近出现很奇怪的抖动。
我把常见问题整理成一个速查表:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 打靶法发散 | 初始协状态猜测太差 | 用直接法先粗解,或用同伦法从简单问题起步 |
| 最优控制总是对手动边界 | 忽略了控制有界性 | 改用极小值原理,检测开关结构 |
| 黎卡提积分数值爆炸 | 积分方向反了 | 从 (t_f) 反向积分到 (t_0) |
| 自由终端时间有多个解 | 缺少 (H(t_f)+...=0) 条件 | 补上终端哈密尔顿横截条件 |
| 开关点附近解抖动 | 网格太粗或未检测过零 | 加密网格,显式求开关函数零点 |
6. 一个能验证全部理论的例子:双积分器时间最优控制
理论说多了容易飘,我们用一个真正算得出来的经典例子把上面的内容串起来。
6.1 问题设置与哈密尔顿函数
考虑双积分器系统:
[ \dot x_1=x_2,\quad \dot x_2=u,\quad |u|\le 1 ]
要求从初始状态到达原点 ((0,0)),并且耗时最短。性能指标是
[ J=\int_{0}^{T}1,dt ]
这个问题的哈密尔顿函数为
[ H=1+\lambda_1 x_2+\lambda_2 u ]
协状态方程为
[ \dot\lambda_1=0,\quad \dot\lambda_2=-\lambda_1 ]
所以 (\lambda_2) 是时间的线性函数。因为 (H) 关于 (u) 线性,最优控制为
[ u=-\operatorname{sign}(\lambda_2) ]
也就是说,控制量要么 +1,要么 -1,只在 (\lambda_2) 过零时切换。由于 (\lambda_2) 是线性函数,它最多只能穿越零点一次,所以最优控制最多只有一个切换点。
终端状态固定,所以不需要 (\lambda(T)) 的横截条件。但终端时间自由,因此必须有
[ H(T)=1+\lambda_1 x_2(T)+\lambda_2(T)u(T)=0 ]
因为目标点是 ((0,0)),所以 (x_2(T)=0),于是得到
[ \lambda_2(T)u(T)=-1 ]
这正好说明在终端时刻,(\lambda_2) 非零,并且符号与终端控制相反。
6.2 Bang-Bang控制与开关曲线
假设初始状态为 (x_1(0)=a>0)、(x_2(0)=0),也就是静止在正半轴某个位置。先从直觉判断:需要先向左加速,再反向制动,轨迹才会停在原点。
第一阶段 (u=-1):
[ x_2(t)=-t,\quad x_1(t)=a-\frac{t^2}{2} ]
第二阶段 (u=+1) 之前,状态要落在开关曲线上。目标原点对应最后一段的进入曲线满足
[ x_2=-\sqrt{2x_1} ]
这个关系来自 (u=+1) 时 (x_2^2=2x_1+C),代入原点定常数 (C=0)。于是开关时刻满足
[ -t=-\sqrt{2\left(a-\frac{t^2}{2}\right)} ]
解得
[ t_s=\sqrt{a} ]
此时
[ x_1=\frac{a}{2},\quad x_2=-\sqrt{a} ]
之后施加 (u=+1),经过同样长的时间把速度减到零。所以总时间为
[ T=2\sqrt{a} ]
这个例子虽然简单,但把所有关键要素都过了一遍:哈密尔顿函数构造、协状态方程、线性切换函数、庞特里亚金极小值条件、自由终端时间的横截条件。如果你能自己把完整过程推一遍,并且画出不同初始位置下的开关曲线,哈密尔顿函数法的基础就真正过关了。
7. 最后聊点我自己的实战习惯
理论框架讲完了,最后分享几个我实际动手时养成的习惯。
7.1 拿到一个新问题,先不要急着列 (\partial H/\partial u=0)
我发现自己早期最容易犯的错,是拿到一个最优控制问题就条件反射地写 (\partial H/\partial u=0)。后来改成先做四件事:第一,写清楚性能指标;第二,写清楚系统状态方程;第三,写清楚终端条件;第四,写清楚控制可行域。只有这四件事都列完了,才去写哈密尔顿函数和必要条件。这样虽然慢一点,但能避免很多无意义的返工。
7.2 用“小问题”检验符号
哈密尔顿函数的符号问题,靠脑子记不靠谱。我的验证方法是用一个自己能解析求解的小例子,比如标量积分器:
[ \dot x=u,\quad J=\int \frac{1}{2}u^2dt ]
这个问题的解可以直接用变分法算出来。如果哈密尔顿函数法写出来的结果和解析解不一致,那就一定是符号定义出了问题。把这个“校验器”固定下来,后面每次换教材、换软件,我都先用它跑一遍,确认自己的公式体系没有漂移。
最后再说一个我觉得被低估的操作:把哈密尔顿函数法得到的必要条件当成一个“可验证的标准答案”,和直接数值优化法的结果交叉验证。我很多次以为打靶法不收敛,最后发现其实是横截条件漏了一项。这个坑踩过之后,我养成了一个习惯——每解一道最优控制题,都先花两分钟把终端条件逐条列出来。这个习惯帮我省下的调试时间,远比那两分钟多。