做车辆队列控制这几年,我最大的心得是:把一个单车的MPC改成多车分布式模型预测控制(DMPC),真正卡人的不是“模型预测”那套理论,而是“分布式”这三个字。车与车之间怎么交换状态、前车的预测轨迹怎么被后车使用、异构参数一加进来为什么原来调好的权重全失效——这些问题不踩一遍,看再多论文也体会不到。这篇博文就以matlab下的异构车辆队列DMPC实现为主线,把建模、控制器设计、代码骨架到仿真调参的完整链路拆开讲,代码框架对应matlab源码15269期的实现思路,整理成可以直接复现的版本。适合正在做智能网联队列协同控制课题的研究生、工程师,也适合想把集中式MPC迁到分布式架构的开发者。
整个实现用到的技术栈其实不复杂:三阶纵向动力学模型、二次规划求解器、再加上一个简单的通信模拟循环。但每个环节都有值得抠的细节,下面按我实际做项目的顺序一条条说。
1. 异构车辆队列DMPC:控制目标、异构来源与架构选择
1.1 控制目标:不是单纯追速,而是排成一条“听话”的队
车辆队列控制听起来像是在做“巡航跟车”,但目标不一样。单车自适应巡航只管本车跟上目标车速、保持安全距离就行;队列控制要求的是整条车队像一个整体一样运动——每一辆车不仅要跟上自己的前车,还要保证误差不会沿车队向后一路放大,否则就会出现“前面轻轻刹一下、后面越刹越猛”的幽灵堵车现象。
用数学语言说,控制目标拆成三层:第一层是间距误差收敛,每辆车与前一车的实际间距要逼近期望间距;第二层是速度跟踪,整个车队要跟踪领航车的速度轨迹;第三层是控制平顺性,加速度和加速度变化率不能来回抖,否则乘坐体验和燃油经济性都完蛋。
1.2 异构性从哪来:参数差异对控制器的直观影响
同一批车出厂铭牌不同,动力学参数自然不同。在matlab实现里最常体现为三个差异:执行器时间常数τ(反映发动机/制动系统对期望加速度的响应快慢)、车长L、以及加速度上下限。τ小的车“脚脚有劲”,τ大的车踩下油门要慢半拍才能顶上。
这个差异对控制器的影响是实打实的。同样的期望加速度命令发给τ=0.3的车和τ=0.8的车,实际加速度曲线能差出一截;在队列起步和制动场景里,这种差异会让后车比前车晚半拍达到目标速度,间距误差瞬时值会明显拉大。所以异构队列的DMPC不能对所有车用同一套预测模型,预测模型里的A、B矩阵必须每辆车各自组装。
1.3 分布式与集中式的取舍:故障容忍与通信代价
集中式MPC把整个车队作为一个大系统,一个优化问题同时求所有车的控制量,理论上最优,但N辆车一多,状态维度就是3N,预测时域一长,QP规模膨胀得很快。更致命的是,一旦中心节点挂了,全队失控。
分布式MPC的思路是每辆车只用自己的本地模型、本地约束求解一个子问题,车与车之间只交换少量信息——通常是前车的预测轨迹或状态参考序列。单车的优化问题始终保持3维状态、与预测时域长度相当的决策量,规模恒定。坏处是拿不到全局最优,属于“协商解”,在队列控制这种强耦合场景下,需要通过信息拓扑设计和权重设置来逼近全局性能。这个取舍在工程上几乎一边倒地选分布式,因为车辆本身就天然具备通信、计算分布式的物理条件。
2. 建模与拓扑:写在公式里的三个关键决策
2.1 三阶纵向动力学模型与离散化
队列控制里最常用的纵向模型是三阶线性模型,把位置p、速度v、加速度a作为状态,期望加速度u作为控制输入:
ṗ_i = v_i
v̇_i = a_i
ȧ_i = (1/τ_i)(u_i − a_i)
第三个方程就是执行器滞后的一阶惯性环节。τ_i越大,实际加速度对期望加速度的跟踪越慢。离散化时,位置和速度用欧拉近似,执行器滞后环节用精确指数离散,这样小采样周期下精度够用,代码也直观:
% 单车离散状态矩阵 Ai = [1, Ts, 0.5*Ts^2; 0, 1, Ts; 0, 0, exp(-Ts/veh(i).tau)]; Bi = [0; 0; 1 - exp(-Ts/veh(i).tau)];注意第三行第三列是指数形式而非(1 − Ts/τ),这是很多初版代码会搞错的地方。当Ts远小于τ时两者接近,但τ只有0.3秒、Ts取0.2秒时误差就明显了,建议直接用指数形式。
2.2 信息流拓扑:PF 与 LPF 的真实差异
队列里车与车之间怎么“看”彼此,决定了每个本地优化问题里参考序列怎么取。最常见的是前车跟随拓扑(PF),每辆车只接收直接前车的状态;更稳的是领航-前车跟随拓扑(LPF),每辆车同时接收前车和领航车的信息。
PF拓扑通信量最小,但对间距策略和权重非常挑剔,速度误差容易沿队列累积。LPF多一路领航车广播,相当于给每辆车一个“全局锚点”,稳定性容错性都好不少。实际工程里领航车轨迹广播本身就是V2V通信的标准功能,所以LPF实现成本只多一个数据接收,强烈建议直接用LPF。
2.3 间距策略:恒定车头时距为什么更稳
期望间距有两种主流写法。恒定间距是d_i = L_i + d0,就是不管车速多少,车间距恒定;恒定车头时距(CTH)是d_i(k) = L_i + h_i·v_i(k),期望间距随本车速度线性变化,h_i是车头时距。
恒定间距策略在低速下能塞更多车,但它在理论上很难做到字符串稳定——间距误差沿队列传递时会被放大,尤其在通信时延存在的场景下几乎必然出事。CTH策略因为期望间距里带了一个与速度相关的项,等价于给系统增加了一个阻尼通道,字符串稳定性条件好满足得多。这也是工程上绝大多数队列系统用CTH的原因。数学上,CTH把间距误差写成:
e_i(k) = p_{i−1}(k) − p_i(k) − L_i − h_i·v_i(k)
后面所有代码都按这个式子来。
3. DMPC 本地优化问题:代价函数、约束与求解器
3.1 本地 QP 的标准形式
每辆车在每个采样时刻求解一个有限时域最优控制问题。对第i辆车,预测时域取Hp,控制时域与预测时域相同,状态预测序列可以写成矩阵形式:
X_i = Φ_i·x_i(k) + Γ_i·U_i
Φ_i是3Hp×3矩阵,Γ_i是3Hp×Hp的下三角块矩阵。这样间距误差、速度偏差、加速度序列全部是U_i的线性函数,代价函数也就整理成标准的二次型:
min ½·Uᵀ·H·U + Fᵀ·U
s.t. lb ≤ U ≤ ub,同时把预测加速度约束写成Aineq·U ≤ bineq
这里的关键是参考序列怎么进F向量。第i辆车在k时刻做预测时,前车第k+j步的位置、速度是已知的参考序列——注意,这个参考序列来自前车上一个采样时刻算出的预测轨迹,而不是前车的当前状态。这点搞错,整个分布式框架就塌了,后面章节单独说。
3.2 代价权重整定:从误差尺度出发的经验方法
我只分享一个自己在matlab里反复用的整定顺序,不搞一堆玄学公式。先把代价函数写成:
J_i = Σ [ q_s·e_i(k+j)² + q_v·(v_i(k+j) − v_ref)² + q_a·a_i(k+j)² ] + Σ [ r·u_i(k+j)² ]
初始权重按误差物理尺度定:间距误差单位是米,数量级1;车速误差单位是米/秒,数量级也是1;加速度是米/秒²。先取q_s=1、q_v=1、q_a=0.1、r=0.1跑通仿真,再逐项调。如果想压低间距误差就把q_s上调,但q_s过大时后车为了追间距会把加速度顶到约束边界,反而引起队列振荡,这个平衡只能在仿真里找。
经验比值:q_s / q_v 通常落在1~5之间,q_a和r取q_s的0.1~0.5倍。领航车那辆(第一辆跟随车)因为直接面对速度参考,q_v可以略大一些,让全队速度基准更稳。
3.3 约束处理与可行性
约束包括控制量上下限(油门/制动能力)和状态约束(加速度上下限)。控制量约束直接写进quadprog的lb/ub;加速度约束需要通过预测方程转成对U的线性不等式,在Aineq里体现。
做DMPC最容易翻车的就是初始步不可行。队列仿真刚开始时,如果初始间距、初始速度与期望间距不符,本地QP可能在第一步就无解。我的处理办法是把间距约束做成软约束——在代价里加一个松弛变量惩罚,而不是硬性不等式。这样即使通信丢包导致参考序列异常,QP也总有解,只是性能下降,不会直接崩掉仿真的主循环。
4. MATLAB 代码骨架:把公式变成能跑的仿真
4.1 异构参数与预测矩阵组装
matlab里组织异构参数,推荐用结构体数组,一辆车一个成员,每个成员有自己的τ、L、h、加速度上下限:
% 异构参数定义 N = 6; % 跟随车数量 Ts = 0.2; % 采样周期 Hp = 20; % 预测时域 for i = 1:N veh(i).tau = 0.3 + 0.5*rand; % 执行器时间常数,故意拉开差异 veh(i).L = 4 + 0.8*rand; % 车长 veh(i).h = 0.8; % 车头时距 veh(i).a_max = 3; veh(i).a_min = -5; veh(i).u_max = 4; veh(i).u_min = -6; end预测矩阵组装是整个程序性能的关键。不少初版代码用三层for循环慢慢填Φ和Γ,车队规模一上去就卡。建议用矩阵运算一次拼好:
% 组装第i辆车的预测矩阵 Phi = zeros(3*Hp, 3); Gamma = zeros(3*Hp, Hp); Ai_pow = eye(3); for j = 1:Hp Ai_pow = Ai_pow * Ai; % A^j Phi(3*j-2:3*j, :) = Ai_pow; for m = 1:j Gamma(3*j-2:3*j, m) = Ai_pow * (Ai^(j-m)) \ Bi; % 简化示意,实际直接保存中间幂 end end实际代码里可以先把Ai^1到Ai^Hp存进cell数组,避免重复求幂。装好Φ、Γ之后,H和F向量直接算:
Qe = kron(eye(Hp), diag([qs, qv, qa])); % 状态加权矩阵 H = 2*(Gamma'*Qe*Gamma + r*eye(Hp)); F = 2*Gamma'*Qe*(Phi*x0 - ref); % ref 是3Hp×1的参考序列:间距参考、速度参考、加速度参考4.2 分布式迭代与通信模拟
这里必须讲清楚分布式协同时序。我的实现采用非迭代式DMPC,也就是每个采样时刻每辆车只解一次QP,不反复交换迭代。具体时序如下:
- 第i−1号车在k时刻先解出自己的预测轨迹全序列;
- 第i号车在k时刻把前车 k+1~k+Hp 步的预测轨迹当作已知参考,解自己的QP;
- 所有车解完后统一推进状态。
这个顺序是串行的,等价于高斯-赛德尔迭代的一轮。另一种做法是所有车同时解,用的是前车在k−1时刻预测的轨迹平移一步。串行版性能更好,因为后车拿到的是前车本轮的最新解;并行版更贴近真实无线通信的广播特点。matlab仿真里两者都容易实现,我建议先做串行版验证算法,再换成并行版测试鲁棒性。
通信丢包也必须在仿真里模拟。实现办法是给每个通信链路加一个独立的伯努利丢包标志,丢包时后车沿用上一帧收到的预测轨迹并向后外推一个周期。没有这一步,你写出的DMPC在理想通信下跑得再漂亮,面对真实V2X信道也稳不住。
4.3 闭环仿真主循环
主循环结构大概是这样:
for k = 1:Tsim % 1. 领航车轨迹更新 v_ref = profile(k); % 2. 每辆车依次解QP for i = 1:N if i == 1 ref_leader = genLeaderRef(v_ref, Hp); Uopt = solveLocalQP(i, x(:,k,i), ref_leader); else pred_prev = TrajPred{i-1}; % 前车本轮预测轨迹 ref_local = buildRef(pred_prev, veh(i).h, veh(i).L); Uopt = solveLocalQP(i, x(:,k,i), ref_local); end TrajPred{i} = Phi_i * x(:,k,i) + Gamma_i * Uopt; u_apply(i) = Uopt(1); end % 3. 状态推进 for i = 1:N x(:,k+1,i) = Ai * x(:,k,i) + Bi * u_apply(i); end end每辆车解QP时都要重新组装H和F,因为参考序列ref每步都在变。这一步是计算热点,N=6、Hp=20时,每步要解6个40维的QP,matlab里用quadprog的interior-point-convex选项跑一圈大约几十毫秒,完全够离线仿真用。如果要实时化,把quadprog换成预编译好的QP求解器或C MEX版本,逻辑不用变。
5. 仿真分析与调参避坑:五个让人头疼的问题
5.1 典型场景与评价指标
仿真场景建议用三段式速度剖面:先是领航车从20 m/s匀加速到30 m/s,再匀速巡航一段,最后匀减速回20 m/s。这个剖面同时覆盖了加速、稳态、制动三个典型工况,足以暴露控制器的大部分问题。
评价指标别只看最终误差收敛,重点看三个:间距误差峰值(加速/制动切换瞬间最容易冒尖)、间距误差沿队列的传播放大比、控制量抖动的剧烈程度。最后一个可以用相邻两步控制量差值的平方和来衡量,matlab里一句diff就行。
5.2 车头时距和权重对队列稳定性的影响
我做过一组对照仿真:同样的异构车队,把车头时距h从0.5调到1.0,其他参数不变。h=0.5时,领航车在30 m/s开始制动,第4号车的间距误差峰值几乎是第1号车的两倍,误差明显沿队列放大;h=1.0时,误差沿队列基本不放大,甚至略有衰减。这说明CTH的h本质上在买“串稳定裕度”,代价是车间距变大、队列占用道路长度变长。
权重的影响类似:q_s过大时,后车为了追间距误差会在前车轻微减速时猛烈制动,控制输入的振荡明显,加速度频繁顶到上限;q_s过小时,间距误差又松弛得让人没法看。我自己常用的做法是把q_s/q_v比值固定在2左右,先调q_a和r让控制平顺,最后回头微调h。
5.3 五个常见坑和处理方法
第一个坑是用前车当前状态当全预测域的参考。前车正在减速时,它未来几步的位置会比当前状态低不少,后车拿当前状态当参考,会高估间距,导致制动力不足。处理办法就是前车用自己的预测轨迹作为后车参考,末端缺失的部分用当前状态外推补齐。
第二个坑是预测矩阵里指数离散与欧拉离散混用导致模型失配。前面已经强调过,τ小时必须用exp(−Ts/τ)。判断方法很简单:对比开环预测的位置和真实积分跑出来的位置,误差在几个厘米以内才算可用。
第三个坑是约束写太多导致频繁不可行。加速度约束、控制量约束、再加上间距硬约束,稍微扰动一下QP就无解。稳妥做法是控制量约束保留硬约束,加速度和间距相关约束做成软约束加惩罚项。
第四个坑是quadprog算法选项选错。Hp=20时40维QP,默认算法在某些matlab版本下求解偏慢。显式指定optimoptions('quadprog','Algorithm','interior-point-convex','Display','none'),速度能快一截,也不会刷一屏迭代信息。
第五个坑是忽略了领航车本身也是一辆车。很多实现把领航车当作理想速度源,只把它的速度剖面当作后车参考,但领航车的实际位置是由纵向动力学积分出来的。如果领航车模型和跟随车不一致,第一辆跟随车的跟踪误差会系统性偏大。正确做法是把领航车也当作编号0的车辆参与动力学积分,只是它不用DMPC,直接按期望速度轨迹开。
说完这五个坑,这套matlab异构队列DMPC基本就能稳定跑出像样的结果了。还有一个小技巧:仿真结果在调试时别只盯曲线,把每辆车每个时刻的QP求解标志输出来看一眼,如果quadprog频繁返回warning,说明约束或参考序列还有问题,这时候再去调参数,方向会明确很多。