别再死记硬背HJB方程了!用Python数值求解一个简单最优控制问题(附完整代码)

张开发
2026/4/13 0:27:40 15 分钟阅读

分享文章

别再死记硬背HJB方程了!用Python数值求解一个简单最优控制问题(附完整代码)
用Python数值求解HJB方程从理论恐惧到工程实践每次翻开最优控制理论的教材Hamilton-Jacobi-Bellman方程总是以一堆偏微分符号的恐怖面目出现让不少工程师和学生在推导中迷失方向。但今天我们要打破这个魔咒——我将带你用Python直接数值求解一个简单的LQR线性二次调节器问题让你亲眼看到HJB方程如何在代码中活起来。这个教程不需要你事先精通泛函分析或随机过程只要你熟悉Python基础语法和numpy库就能跟随我们一步步实现建立简单的车辆位置控制模型将HJB方程离散化为有限差分形式用迭代法数值求解价值函数可视化收敛过程与最优控制策略1. 问题建模一个直观的控制案例让我们从一个经典的物理系统开始——控制一辆小车移动到原点。设x(t)表示小车位置u(t)表示控制力加速度系统动力学为def system_dynamics(x, u, dt): 一维小车运动模型 return x u * dt # 简单积分模型对应的成本函数采用标准二次型def running_cost(x, u): 瞬时成本函数 return 0.5 * (x**2 u**2) # 平衡状态偏离与控制能耗提示这个简单的LQR问题有解析解但我们将故意忽略它纯粹用数值方法求解HJB方程。2. HJB方程的离散化处理连续时间的HJB方程难以直接计算我们需要将其离散化。对时间t和状态x进行网格划分import numpy as np # 参数设置 T 10.0 # 总时长 dt 0.1 # 时间步长 x_max 5.0 # 状态空间范围 dx 0.1 # 状态步长 # 创建网格 t_grid np.arange(0, Tdt, dt) x_grid np.arange(-x_max, x_maxdx, dx) u_grid np.linspace(-2, 2, 21) # 控制输入候选值将偏导数用有限差分近似∂V/∂t ≈ (V(t,x) - V(t-dt,x))/dt∂V/∂x ≈ (V(t,xdx) - V(t,x-dx))/(2dx)3. 值迭代算法实现基于离散化公式我们可以实现值迭代算法def solve_hjb(): # 初始化价值函数 V np.zeros((len(t_grid), len(x_grid))) V[-1, :] 0.5 * x_grid**2 # 终端条件 # 存储最优控制策略 policy np.zeros_like(V) # 反向时间迭代 for k in range(len(t_grid)-2, -1, -1): for i, x in enumerate(x_grid): # 计算空间导数 if i 0: dVdx (V[k1, i1] - V[k1, i]) / dx elif i len(x_grid)-1: dVdx (V[k1, i] - V[k1, i-1]) / dx else: dVdx (V[k1, i1] - V[k1, i-1]) / (2*dx) # 寻找最优控制 min_cost np.inf for u in u_grid: # 预测下一状态 x_next x u * dt x_next np.clip(x_next, -x_max, x_max) # 找到最近的网格点 j np.argmin(np.abs(x_grid - x_next)) # 计算总成本 cost running_cost(x, u) * dt V[k1, j] if cost min_cost: min_cost cost best_u u # 更新价值函数和策略 V[k, i] min_cost policy[k, i] best_u return V, policy4. 可视化与结果分析求解完成后我们可以用matplotlib观察结果import matplotlib.pyplot as plt from mpl_toolkits.m3d import Axes3D # 绘制价值函数曲面 T_mesh, X_mesh np.meshgrid(t_grid, x_grid, indexingij) fig plt.figure(figsize(12, 5)) ax fig.add_subplot(121, projection3d) ax.plot_surface(T_mesh, X_mesh, V, cmapviridis) ax.set_title(Value Function V(t,x)) # 绘制最优策略 ax fig.add_subplot(122) contour ax.contourf(T_mesh, X_mesh, policy, levels20) plt.colorbar(contour) ax.set_title(Optimal Policy u*(t,x)) plt.tight_layout() plt.show()你会看到两个关键图形价值函数曲面呈现漏斗形状越接近终点时间T价值函数越接近终端条件最优策略分布显示状态空间不同位置应采取的最优控制力5. 工程实践中的注意事项在实际应用中我们还需要考虑几个关键问题边界条件处理如何设置合理的状态边界影响求解稳定性收敛性判断迭代何时停止可以监测价值函数变化量def relative_error(V_old, V_new): return np.max(np.abs(V_new - V_old) / (np.abs(V_old) 1e-6))维度灾难状态变量增加时计算量指数增长这时需要考虑稀疏网格方法深度学习近似如Physics-Informed Neural Networks6. 扩展应用从LQR到更复杂场景虽然我们演示的是简单LQR问题但相同方法论可以扩展到非线性系统只需修改system_dynamics函数随机控制加入噪声项并采用条件期望高维问题虽然计算量增加但原理相同# 非线性系统示例 def nonlinear_dynamics(x, u, dt): return x (np.sin(x) u) * dt # 随机系统示例 def stochastic_dynamics(x, u, dt): noise np.random.normal(scale0.1) return x u * dt noise * np.sqrt(dt)7. 性能优化技巧当处理更复杂问题时这些优化手段很关键向量化计算避免Python循环改用numpy广播并行化控制候选u的评估可以并行进行自适应网格在变化剧烈区域加密网格热启动用粗网格解初始化细网格计算# 向量化计算示例 def vectorized_update(V_next, x_grid, u_grid, dt, dx): # 预计算空间导数 dVdx np.zeros_like(x_grid) dVdx[1:-1] (V_next[2:] - V_next[:-2]) / (2*dx) dVdx[0] (V_next[1] - V_next[0]) / dx dVdx[-1] (V_next[-1] - V_next[-2]) / dx # 广播计算所有u的成本 x_next x_grid[:, None] u_grid[None, :] * dt x_next np.clip(x_next, -x_max, x_max) # ...其余向量化计算...8. 常见问题排查指南调试HJB数值解时这些问题最常出现现象可能原因解决方案价值函数爆炸时间步长太大减小dt或使用隐式方法策略振荡状态网格太粗细化dx或使用高阶差分收敛慢初始猜测差从终端条件反向传播边缘异常边界条件不当检查边界处理逻辑9. 完整代码架构为了更好的工程实践建议采用面向对象方式组织代码class HJBSolver: def __init__(self, params): self.dt params[dt] self.dx params[dx] # 初始化网格和其他参数 def dynamics(self, x, u): # 定义系统动力学 pass def running_cost(self, x, u): # 定义瞬时成本 pass def solve(self): # 实现求解逻辑 pass def visualize(self): # 结果可视化 pass # 使用示例 params {dt: 0.1, dx: 0.1, T: 10.0} solver HJBSolver(params) V, policy solver.solve() solver.visualize()10. 进一步学习路径掌握基础实现后可以深入以下方向理论层面粘性解概念验证定理证明随机控制扩展计算层面水平集方法快速行进法GPU加速实现应用领域机器人路径规划量化金融最优执行能源系统调度在GitHub上可以找到许多优质开源实现如FEniCS用于有限元求解PDEPyPDES专门针对HJB方程的求解器DeepHJB基于深度学习的方法

更多文章