第三讲:约束处理——硬约束与软约束、松弛因子引入、约束违反代价设计

各位同学,欢迎来到第三讲。

上一讲我们聊了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里是绝对不能接受的。

⚠️ 避坑指南:我曾经在一个AEB项目中,把最小跟车距离设成了硬约束。结果有一次前车急刹,我们的车距离前车只剩4米,而约束是5米。优化器直接报错,输出保持上一时刻的制动压力,导致制动延迟。从那以后,我再也不敢把所有约束都设成硬约束了。

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

你看,安全类约束的 ρ 设得很大,但松弛因子范围很小。这意味着:你可以轻微违反,但代价极高。而舒适类约束的 ρ 设得小,松弛因子范围大,意味着:在紧急情况下,可以牺牲舒适性来保证安全

💡 实战技巧:我建议你在调试时,先给所有约束都加上松弛因子,但把安全类约束的 ρ 设成硬约束的100倍。这样,在正常工况下,软约束表现和硬约束一样;但在极端工况下,求解器有“退路”,不会直接崩溃。

4. 约束违反代价设计:从工程角度思考

约束违反代价的设计,说白了就是回答一个问题:违反这个约束,到底有多严重?

我一般从三个维度来设计:

  1. 安全性权重:违反安全约束的代价应该是舒适性约束的100~1000倍。我在项目中习惯把安全约束的 ρ 设为10000,舒适性约束的 ρ 设为50。
  2. 时变权重:随着车速变化,约束违反的代价也应该变化。比如,高速时偏离车道中心的代价应该比低速时大得多。我常用一个与车速成正比的系数来调整 ρ。
  3. 预测时域内的权重分布:越靠近当前时刻的约束违反,代价应该越大。因为近期的约束违反是“即将发生”的,而远期的约束违反还有机会通过后续控制来修正。

代码实现上,我通常这样设计:

# 约束违反代价设计示例(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的求解器选择和实时性优化,到时候我会分享一些让MPC跑得更快的“黑科技”。

下课!