☰
哈密尔顿函数法详解:从变分法到最优控制的核心框架
2026/10/4 6:12:29 网站建设 项目流程

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 间接法数值求解:从猜初始协状态开始

间接法打靶的流程很直接:

  1. 写出哈密尔顿函数,求出最优控制关于状态和协状态的表达式。
  2. 把控制表达式代回状态方程和协状态方程,形成一个关于 ((x,\lambda)) 的增广常微分方程组。
  3. 猜测缺失的初始边界条件,通常是 (\lambda(t_0));如果终时自由,还要猜 (t_f)。
  4. 从 (t_0) 正向积分到 (t_f),检查终端横截条件是否满足。
  5. 不满足就用牛顿法或拟牛顿法更新猜测值,继续迭代。

这套方法看起来简单,实际用起来很考验初始猜测。尤其当系统具有不稳定模态时,状态、协状态会在积分末端指数爆炸,稍微偏差一点就面目全非。更稳的选择是多重打靶:把时间区间分成若干段,每一段都从段首积分,并引入连续的接续条件。这样能显著降低对初始猜测的敏感度。现在的边界值问题求解器,比如 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 ]

这个问题的解可以直接用变分法算出来。如果哈密尔顿函数法写出来的结果和解析解不一致,那就一定是符号定义出了问题。把这个“校验器”固定下来,后面每次换教材、换软件,我都先用它跑一遍,确认自己的公式体系没有漂移。

最后再说一个我觉得被低估的操作:把哈密尔顿函数法得到的必要条件当成一个“可验证的标准答案”,和直接数值优化法的结果交叉验证。我很多次以为打靶法不收敛,最后发现其实是横截条件漏了一项。这个坑踩过之后,我养成了一个习惯——每解一道最优控制题,都先花两分钟把终端条件逐条列出来。这个习惯帮我省下的调试时间,远比那两分钟多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询