简介:这份资源面向控制工程、自动化及车辆工程方向的学习者与研究者,聚焦LQR控制器设计中加权矩阵难以确定这一痛点,提供一套基于遗传算法进行全局寻优的完整案例。资源以汽车主动悬架为被控对象,将性能指标作为目标函数,利用遗传算法的交叉、变异与适者生存机制优化加权矩阵,从而提升LQR的设计效率与控制性能,适合具备一定MATLAB基础、希望深入理解最优控制与智能优化结合的中高级读者。压缩包共7个文件,约97KB,包含2个m脚本、2个mat数据文件、1个xml配置、1个mdl仿真模型及1个slxc缓存文件,覆盖算法实现、参数存储与Simulink建模等环节。目前已有266人学习下载。通过该案例,读者可掌握遗传算法与LQR联合设计的完整流程,理解目标函数构造、权重矩阵编码与仿真验证思路,并可直接运行模型观察主动悬架性能变化,为自身课题或工程实践提供可复用的参考方案。
1. 遗传算法整定 LQR 权重的真实动机:为什么手动调 Q 和 R 总是差一口气
拿倒立摆、四旋翼悬停或者二连杆机械臂做 LQR,很多人第一步就卡在权重矩阵上。Q 和 R 一改,闭环极点全变,响应不是超调大就是收敛慢,手动试凑半天,最后交上去的曲线自己都不好意思看。LQR 本身有解析解,Riccati 方程一解,增益 K 就出来了,可它默认你给的 Q、R 就是对的——问题恰恰出在这。遗传算法(GA)在这里的角色不是替代 LQR,而是把「选权重」这件事从人肉试错变成带目标函数的搜索。这篇笔记就围绕「基于遗传算法的 LQR 控制器优化设计」这条线,把编码方式、目标函数、参数边界和 MATLAB 落地步骤讲清楚,适合已经会写 LQR、但被权重折磨过的控制方向从业者。
2. 先搞清楚 GA 在优化什么:LQR 权重编码与目标函数设计
2.1 为什么优化对象是 Q 而不是 K
LQR 的标准形式是最小化代价函数
J = ∫ (xᵀQx + uᵀRu) dt
其中 Q 是状态权重矩阵,R 是控制权重矩阵。对线性系统 ẋ = Ax + Bu,最优增益 K = R⁻¹BᵀP,P 由代数 Riccati 方程解出。也就是说,K 是 Q、R 的隐函数,你直接搜 K 会破坏 LQR 的最优性保证,搜出来的增益可能根本不对应任何正定 Q、R。所以 GA 的决策变量应该是 Q 和 R 的对角元素,而不是 K 本身。
常见做法是取 Q = diag(q1, q2, …, qn),R = diag(r1, r2, …, rm),把 q 和 r 拉成一个实数向量作为个体。为了减少搜索维度,工程上一般固定 R = I,只优化 Q;或者固定 Q 中某个基准项,只调相对比例。这一步的取舍直接决定搜索空间大小,维度越高 GA 越难收敛。
2.2 目标函数怎么写才不坑自己
GA 需要一个标量适应度来评价每个权重组合的好坏。最直接的是用 ITAE(时间乘绝对误差积分):
ITAE = ∫ t·|e(t)| dt
它同时惩罚稳态误差和收敛时间,比 ISE 更看重后期误差。但只用 ITAE 会有一个经典翻车:GA 为了压低误差,把控制量推到极大,实际系统根本执行不了。所以目标函数里必须加控制量惩罚项,常见形式是
fitness = ∫ t·|e(t)| dt + w·∫ u²(t) dt
w 是惩罚系数,取 0.01~0.1 之间比较稳。如果系统有超调硬约束,还可以加一项 max(0, overshoot - overshoot_max)² 作为罚函数。
注意:适应度函数里不要直接调用 sim 命令跑 Simulink,每次仿真启动开销极大,GA 迭代几百代直接跑不动。用 lsim 或者自己写 RK4 积分,速度差一个数量级。
2.3 个体编码与边界设置
用实数编码,每个基因对应一个 q 或 r。边界不能拍脑袋,下限取一个很小的正数(比如 1e-3)防止 Q 非正定,上限根据状态量纲来定:位置量权重可以到 1e4,速度量到 1e2,控制量权重一般不超过 1e3。边界给太宽,GA 前期会浪费大量代数在无效区域;给太窄,最优解可能被卡在边界上。
% 个体编码示例:Q 取 4 个状态权重,R 固定为 1 nVars = 4; lb = [1e-3, 1e-3, 1e-3, 1e-3]; % 下限,保证正定 ub = [1e4, 1e4, 1e2, 1e2]; % 上限按状态量纲给 % 适应度函数句柄 fitnessFcn = @(q) lqr_fitness(q, A, B, C, D, w_u);这段代码定义了一个 4 维搜索空间,lb 和 ub 分别是个体每一维的上下界。lqr_fitness 内部先构造 Q = diag(q),R = 1,调用 lqr 得到 K,再用 lsim 跑闭环算 ITAE 加控制惩罚。参数 w_u 控制控制量惩罚力度,建议从 0.05 开始试。
3. 用 MATLAB 把 GA 和 LQR 接起来:从脚本到可复现结果
3.1 适应度函数的完整实现
function J = lqr_fitness(q, A, B, C, D, w_u) % q: 1x4 权重向量 Q = diag(q); R = 1; try K = lqr(A, B, Q, R); catch J = 1e10; % 求解失败给大惩罚 return; end sys_cl = ss(A - B*K, B, C, D); t = 0:0.01:10; [y, t_out, x] = lsim(sys_cl, zeros(size(t)), t); e = y; % 假设参考输入为 0,初始状态响应 ITAE = sum(t_out .* abs(e)) * 0.01; u = -K * x'; J = ITAE + w_u * sum(u.^2) * 0.01; end逻辑说明:先由 q 构造 Q,R 固定为 1,调用 lqr 解 Riccati 得到 K。如果 Q 导致求解失败(比如数值病态),直接返回一个很大的适应度,GA 自然淘汰这个个体。然后用闭环状态空间模型跑 10 秒仿真,步长 0.01 秒,计算 ITAE 和控制量平方积分。参数 w_u 是控制惩罚权重,0.05 左右对多数欠驱动系统比较合适;如果执行器饱和明显,可以加到 0.1。
3.2 GA 参数怎么设才不白跑
MATLAB 的 ga 函数关键参数就几个:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| PopulationSize | 30~50 | 维度 4~6 时 30 够用,维度高加到 50 |
| MaxGenerations | 100~200 | 看适应度曲线是否平台化,一般 150 代内收敛 |
| CrossoverFraction | 0.8 | 默认值,实数编码下表现稳定 |
| MutationFcn | @mutationadaptfeasible | 自适应变异,适合有边界约束 |
| EliteCount | 2 | 保留最优个体,防止退化 |
options = optimoptions('ga', ... 'PopulationSize', 40, ... 'MaxGenerations', 150, ... 'CrossoverFraction', 0.8, ... 'MutationFcn', @mutationadaptfeasible, ... 'EliteCount', 2, ... 'Display', 'iter', ... 'PlotFcn', @gaplotbestf); [q_opt, fval] = ga(fitnessFcn, nVars, [], [], [], [], lb, ub, [], options); Q_opt = diag(q_opt); K_opt = lqr(A, B, Q_opt, 1);这段代码把 GA 跑起来,输出最优权重向量 q_opt 和对应适应度 fval。Display 设为 iter 可以看到每代最优值,PlotFcn 画收敛曲线。跑完后用 Q_opt 重新解一次 LQR 得到最终增益 K_opt。
3.3 结果验证:别只看适应度曲线
GA 跑完不代表结束。必须做三件事:第一,把 K_opt 代回闭环,画阶跃响应和初始状态响应,看超调、调节时间、稳态误差是否满足要求;第二,检查控制量 u 是否超出执行器限幅;第三,换一组不同的初始状态再跑一遍,确认不是针对单一工况过拟合。
sys_final = ss(A - B*K_opt, B, C, D); step(sys_final); title('GA-LQR 闭环阶跃响应'); % 检查控制量 [y, t, x] = lsim(sys_final, zeros(size(t)), t); u_final = -K_opt * x'; plot(t, u_final); ylabel('控制量 u');如果阶跃响应有稳态误差,说明 Q 中对应状态权重还不够大,或者系统本身没有积分环节。这时候要么加积分器扩维,要么在目标函数里额外惩罚稳态误差。控制量曲线如果贴着限幅值,说明 w_u 给小了,回去加大重跑。
4. 避坑与排查:GA-LQR 联调时最容易翻车的 5 个地方
4.1 现象:GA 跑了 200 代适应度几乎不降
原因:搜索空间维度太高,或者边界给得太宽,初始种群撒得太散。另一个常见原因是适应度函数里用了固定随机种子,导致每个个体评价不稳定。
解决:先把 Q 的维度压到 2~3 个关键状态,其余固定为 1。边界按状态量纲缩小一个数量级。适应度函数里不要引入随机性,仿真初始状态固定。
4.2 现象:最优解落在边界上
原因:真实最优权重超出了你给的上限或下限。GA 在边界附近反复试探但出不去。
解决:看 q_opt 哪个分量贴边,把那一维的边界放宽 5~10 倍再跑一次。如果放宽后最优值还是贴边,说明目标函数或系统模型有问题,不是 GA 的问题。
4.3 现象:适应度很好但实际响应振荡
原因:目标函数只算了 ITAE,没有惩罚超调或控制量变化率。GA 找到了一组权重让误差积分很小,但代价是控制量高频抖振。
解决:在适应度里加控制量变化率惩罚 sum(diff(u).^2),或者加超调罚函数。权重取 ITAE 的 0.1~0.3 倍。
4.4 现象:每次跑 GA 结果都不一样
原因:ga 默认使用随机初始种群,没有固定随机种子。这不是 bug,是 GA 的随机搜索特性。
解决:如果要做对比实验,用 rng(42) 固定种子。如果只是要一个可用解,多跑几次取适应度最好的那组。一般跑 3~5 次,取最优。
4.5 现象:lqr 函数报错或返回空
原因:Q 或 R 不是正定矩阵,或者系统 (A,B) 不可稳。GA 在搜索过程中可能生成非正定的 Q。
解决:在适应度函数里用 try-catch 包住 lqr 调用,失败返回大惩罚值。同时检查 (A,B) 的可稳性:rank(ctrb(A,B)) 是否等于系统阶数。
5. 进阶技巧:用并行计算和混合策略把 GA-LQR 压到可接受耗时
GA 优化 LQR 最大的实际障碍是耗时。每次适应度评价要解一次 Riccati 加一次仿真,40 个个体跑 150 代就是 6000 次评价。如果单次评价 0.05 秒,总耗时 5 分钟,还能忍;但如果系统阶数高、仿真步长小,单次评价到 0.5 秒,总耗时 50 分钟,调参体验就很差了。
第一个提速手段是并行化。MATLAB 的 ga 支持 UseParallel 选项,配合 parpool 可以把种群评价分发到多核。
if isempty(gcp('nocreate')) parpool('local', 4); end options = optimoptions('ga', options, 'UseParallel', true);这段代码启动 4 个 worker,ga 会自动把每代种群分成 4 份并行评价。实测在 4 核机器上加速比大约 3.2~3.6,因为有个体评价时间不均匀和通信开销。注意并行池启动本身要几秒,如果总评价次数少于 500 次,并行反而更慢。
第二个手段是混合策略:先用 GA 粗搜,再用 fmincon 局部精调。GA 负责跳出局部最优,fmincon 负责在 GA 结果附近快速收敛到精确解。
% GA 跑完后,以 q_opt 为初值做局部优化 problem.solver = 'fmincon'; problem.objective = fitnessFcn; problem.x0 = q_opt; problem.lb = lb; problem.ub = ub; problem.options = optimoptions('fmincon', 'Display', 'iter'); [q_final, fval_final] = fmincon(problem);这个混合流程通常比纯 GA 少用 30%~50% 的评价次数,最终适应度还能再降 5%~15%。我一般会先跑 50 代 GA,看适应度曲线是否已经平台化,如果还在降就继续跑,平台化了就切 fmincon。
第三个手段是降阶。如果原系统是 6 阶以上,先做平衡截断或者模态截断,把用于优化的模型降到 3~4 阶,GA 搜出来的权重再代回全阶模型验证。这一步有风险:降阶模型的最优权重不一定对全阶模型最优,所以验证环节不能省。我自己的习惯是降阶模型只用来缩小搜索范围,最终权重一定在全阶模型上重新评价一次,如果适应度退化超过 20%,就放弃降阶结果,老老实实全阶跑。
最后一个技巧是适应度缓存。GA 在交叉变异过程中可能生成重复个体,尤其是种群规模小、代数多的时候。用一个 Map 容器缓存已评价过的 q 向量和对应适应度,重复个体直接查表,能省 10%~20% 的评价次数。实现上用一个 containers.Map,key 是 q 的哈希字符串,value 是适应度值。这个优化在维度低的时候效果一般,维度到 6 以上、种群 50 以上时比较明显。
这些手段叠加下来,一个 4 维 Q 优化、150 代 GA 的典型任务,从单核 8 分钟压到 4 核加缓存加混合策略的 1 分半左右,调参迭代速度就舒服多了。希望帮到你。
本文还有配套的精品资源,点击获取