第三讲:约束处理——硬约束与软约束、松弛因子引入、约束违反代价设计
各位同学,欢迎来到第三讲。
上一讲我们聊了MPC的预测模型和代价函数设计。今天要聊的,是MPC里最让人头疼,也最体现工程水平的部分——约束处理。
说实话,在ADAS里,约束就是你的“安全底线”。你设计的控制器再花哨,如果约束没处理好,车该停的时候没停住,或者方向盘打过了头,那后果不堪设想。我在项目里见过太多因为约束设置不合理导致的“幽灵刹车”和“方向盘抖动”问题。嗯,今天我们就来把这些坑填上。
1. 硬约束:说一不二的底线
硬约束,顾名思义,就是绝对不能违反的约束。在ADAS里,典型的硬约束包括:
- 执行器物理极限:比如方向盘最大转角、制动最大减速度、油门最大开度。你想想看,电机物理上就只能转那么多,你非要它转更多,那不是强人所难吗?
- 安全边界:比如与前车的安全距离、车道边界线。这些是“红线”,碰了就可能出事故。
在MPC的数学表达里,硬约束长这样:
# 硬约束示例(伪代码)
# 状态约束:车速不能超过120 km/h
x(k) <= 120.0
# 控制量约束:方向盘转角在[-45°, 45°]之间
-45.0 <= u(k) <= 45.0
# 输出约束:与前车距离必须大于5米
y(k) >= 5.0
但是,硬约束有个大问题——求解可行性。如果当前状态已经违反了硬约束,或者未来预测的轨迹必然违反硬约束,那优化问题就无解了。控制器会直接“罢工”,输出一个NaN或者保持上一时刻的控制量。这在ADAS里是绝对不能接受的。
2. 软约束:给约束“松绑”
为了解决硬约束的“死板”问题,我们引入了软约束。说白了,就是允许约束被轻微违反,但要在代价函数里“惩罚”这种违反行为。
软约束的数学表达通常引入一个松弛变量(slack variable),记作 ε。约束变成:
# 软约束示例
# 原来的硬约束:y(k) >= 5.0
# 变成软约束:y(k) + ε >= 5.0, 其中 ε >= 0
# 在代价函数里加入惩罚项:
J = J_original + ρ * ε^2
# 或者 J = J_original + ρ * ε (L1范数惩罚)
这里的 ρ 就是约束违反代价的权重。ρ 越大,对违反约束的惩罚越重,软约束就越接近硬约束。ρ 越小,约束越容易被违反。
我个人习惯用二次型惩罚(ρ * ε²),因为它的梯度是连续的,求解器更容易收敛。但L1范数惩罚(ρ * ε)也有好处——它不会过度惩罚小的违反,更适合处理传感器噪声带来的微小波动。
3. 松弛因子:怎么选?
松弛因子 ε 的引入,其实给了我们一个“权衡”的空间。在ADAS里,我通常把松弛因子分成两类:
| 类型 | 典型场景 | 松弛因子范围 | 惩罚权重 ρ |
|---|---|---|---|
| 安全类约束 | 与前车距离、车道边界 | ε ∈ [0, 0.5] 米 | ρ = 1000 ~ 10000 |
| 舒适类约束 | 加速度变化率、方向盘角速度 | ε ∈ [0, 2.0] 单位 | ρ = 10 ~ 100 |
| 执行器极限 | 最大转角、最大制动压力 | ε ∈ [0, 0.1] 单位 | ρ = 5000 ~ 50000 |
你看,安全类约束的 ρ 设得很大,但松弛因子范围很小。这意味着:你可以轻微违反,但代价极高。而舒适类约束的 ρ 设得小,松弛因子范围大,意味着:在紧急情况下,可以牺牲舒适性来保证安全。
4. 约束违反代价设计:从工程角度思考
约束违反代价的设计,说白了就是回答一个问题:违反这个约束,到底有多严重?
我一般从三个维度来设计:
- 安全性权重:违反安全约束的代价应该是舒适性约束的100~1000倍。我在项目中习惯把安全约束的 ρ 设为10000,舒适性约束的 ρ 设为50。
- 时变权重:随着车速变化,约束违反的代价也应该变化。比如,高速时偏离车道中心的代价应该比低速时大得多。我常用一个与车速成正比的系数来调整 ρ。
- 预测时域内的权重分布:越靠近当前时刻的约束违反,代价应该越大。因为近期的约束违反是“即将发生”的,而远期的约束违反还有机会通过后续控制来修正。
代码实现上,我通常这样设计:
# 约束违反代价设计示例(Python风格伪代码)
def compute_slack_penalty(violation, speed, time_horizon):
# 基础权重
base_weight = 1000.0 # 安全约束
# 时变权重:车速越高,权重越大
speed_factor = 1.0 + speed / 100.0 # 假设最高车速100 m/s
# 时域权重:越近的步长,权重越大
time_factor = 1.0 / (1.0 + 0.1 * time_horizon)
# 最终代价
penalty = base_weight * speed_factor * time_factor * (violation ** 2)
return penalty
这里有个细节要注意:violation 的平方。如果 violation 是负的(即没有违反约束),平方后还是正数,但实际不应该有惩罚。所以一定要加一个判断:
if violation > 0:
penalty = compute_slack_penalty(violation, ...)
else:
penalty = 0.0
5. 实战中的常见问题与调试方法
讲到这里,我分享几个我在项目中踩过的坑:
- 问题1:软约束导致系统“偷懒”。如果你把 ρ 设得太小,控制器会发现“违反约束比满足约束更划算”,从而故意违反约束。解决办法:把 ρ 设大,或者对松弛因子 ε 本身也加一个惩罚。
- 问题2:松弛因子振荡。当约束刚好在边界上时,松弛因子可能会来回跳动,导致控制量抖动。我一般会在松弛因子的变化率上也加一个惩罚,或者对 ε 做低通滤波。
- 问题3:多个约束同时违反。当多个约束同时被违反时,松弛因子之间会“打架”。我的做法是:给每个约束分配独立的松弛因子,而不是共用一个。虽然计算量大了点,但调试起来清晰很多。
好了,这一讲就到这里。下一讲我们会聊MPC的求解器选择和实时性优化,到时候我会分享一些让MPC跑得更快的“黑科技”。
下课!