一、最优控制理论回顾:变分法基础、庞特里亚金极小值原理、动态规划与HJB方程
各位同学,欢迎来到《最优制导律数值求解与代码实现》的第一章。
说实话,做制导控制这些年,我最大的体会是:最优控制理论不是一堆数学公式,而是我们设计“聪明”导弹的底层逻辑。你想想看,一枚导弹要拦截高速目标,怎么飞最省燃料?怎么转弯最敏捷?这些问题,归根结底都要回到我们今天要讲的这三个核心工具上。
我个人习惯把这一章叫做“工具箱”。变分法是螺丝刀,极小值原理是扳手,动态规划和HJB方程则是那把多功能瑞士军刀。咱们一个一个拆开看。
1.1 变分法基础:从“求极值”到“求轨迹”
咱们先从一个简单问题入手。普通函数求极值,你肯定熟悉——导数等于零。但最优控制里,我们要求的是一条轨迹,而不是一个点。
举个例子。你从A点开车到B点,想找一条时间最短的路径。这不是找一个坐标,而是找一条曲线。变分法就是干这个的。
核心思想其实就一句话:让性能指标J的“一阶变分”等于零。
数学上,我们定义性能指标:
J = ∫ L(x, u, t) dt
其中L是拉格朗日函数,x是状态,u是控制。
变分法给出的必要条件——欧拉-拉格朗日方程:
∂L/∂x - d/dt(∂L/∂ẋ) = 0
嗯,这里要注意。这个方程只适用于无约束的情况。我在项目中遇到过有人直接拿它去解带控制约束的问题,结果算出来的轨迹根本不可行。为什么?因为导弹的舵面偏转角是有极限的,你不能让它转180度。
1.2 庞特里亚金极小值原理:带约束的“最优开关”
庞特里亚金极小值原理(PMP),说白了就是变分法的“升级版”。它最大的贡献是:能处理控制有约束的情况。
核心思路是引入协态变量λ(t)和哈密顿函数H:
H = L(x, u, t) + λᵀ · f(x, u, t)
然后最优控制u*必须满足:
H(x*, u*, λ*, t) ≤ H(x*, u, λ*, t) 对所有允许的u成立
这句话翻译成人话就是:在最优轨迹上,哈密顿函数取最小值。这就是“极小值”这个名字的由来。
我个人的经验是,PMP特别适合处理“bang-bang控制”问题。比如导弹在末端需要大过载机动,这时候控制量往往直接打到边界上。用PMP,你能清晰地看到什么时候该“开”,什么时候该“关”。
来看一个简单的代码示例,用数值方法求解一个带约束的最优控制问题:
import numpy as np
from scipy.optimize import minimize
# 定义系统动力学:dx/dt = u,控制约束 |u| ≤ 1
def dynamics(x, u):
return u
# 定义哈密顿函数
def hamiltonian(x, u, lam):
return 0.5 * u**2 + lam * u
# 数值求解协态方程
def solve_costate(x_history, u_history, dt):
lam = np.zeros_like(x_history)
lam[-1] = 0 # 终端条件
for i in range(len(x_history)-2, -1, -1):
# 协态方程:dλ/dt = -∂H/∂x = 0 (本例中H不显含x)
lam[i] = lam[i+1]
return lam
# 极小值条件:u* = -sat(λ) (饱和函数)
def optimal_control(lam):
u = -lam
u = np.clip(u, -1.0, 1.0) # 约束处理
return u
1.3 动态规划与HJB方程:从“全局最优”到“实时决策”
动态规划的思路很朴素:如果一个问题可以分解成子问题,那么最优策略的子策略也是最优的。这就是贝尔曼最优性原理。
但直接做动态规划有个致命问题——维度灾难。状态空间稍微大一点,网格就爆炸了。我记得有一次做三维制导问题,状态变量有6个,每个方向分100个网格,你猜内存要多大?根本算不动。
于是就有了HJB方程。它是动态规划的连续形式:
-∂V/∂t = min_u { L(x, u, t) + (∂V/∂x)ᵀ · f(x, u, t) }
其中V(x,t)是值函数,表示从当前状态到终点的最优代价。
HJB方程是个偏微分方程,解析解很难求。但在制导领域,我们经常用数值方法来近似求解。比如用有限差分法或者神经网络逼近。
下面是一个简单的值函数迭代代码框架:
import numpy as np
# 离散化状态空间
x_grid = np.linspace(-5, 5, 101)
V = np.zeros_like(x_grid) # 初始猜测
# 迭代求解HJB方程
for iteration in range(100):
V_new = V.copy()
for i, x in enumerate(x_grid):
# 对每个状态,搜索最优控制
best_value = np.inf
for u in [-1.0, 0.0, 1.0]: # 离散控制集
# 计算下一步状态(简单欧拉法)
x_next = x + u * 0.1
# 找到x_next对应的网格索引
j = np.argmin(np.abs(x_grid - x_next))
# 计算代价
cost = 0.5 * u**2 * 0.1 + V[j]
if cost < best_value:
best_value = cost
V_new[i] = best_value
V = V_new
print("值函数迭代完成")
1.4 三者的关系与选择
讲了这么多,你可能有点晕。这三个工具到底什么时候用哪个?
我画了一张图,帮你理清思路:
我个人在实际项目中的做法是:先用动态规划的思路理解问题结构,再用PMP写出必要条件,最后用数值方法求解。这三者不是互斥的,而是互补的。
好了,这一章的内容就到这里。记住这三个工具的名字和适用场景,后面几章我们会反复用到它们。
公众号:蓝海资料掘金营,微信deep3321