做控制仿真的同学应该都有过这种经历读论文时觉得无模型自适应控制MFAC很清晰无非是动态线性化、伪偏导数估计、加权一步预测控制三步走。可真到写代码的时候伪偏导数的初值该给多少、重置条件要不要写、控制器里的 λ 调成多少、MIMO 矩阵运算为什么会报“singular matrix”——每个问题都能卡住一下午。这篇博文是对我整理的一套 MFAC 仿真资料包的完整复盘。包里有六套可运行的仿真程序覆盖 SISO/MIMO、CFDL/PFDL 两类动态线性化、伪偏导数估计的完整实现还配了一批按阅读顺序整理好的参考资料。我尽量把“书上没明说但跑仿真必须知道”的细节也写出来适合正在复现 MFAC、准备往论文方向扩展、或者只想快速跑通一个无模型控制器看看效果的人。这篇文章不会先从“什么是 MFAC”开始背书而是直接回答三个更实际的问题这六套仿真为什么这样拆每套程序的伪偏导数和控制律到底怎么写跑不通、跑不平、跑出 NaN 时应该查哪里1. 为什么值得把六套MFAC仿真放在一起从CFDL到PFDL再到MIMO的演进路径我最初接触 MFAC 时只看单个 SISO 的 CFDL 例子。当时觉得逻辑很顺非线性系统在相邻采样点上可以写成输入增量与输出增量之间的等价线性关系然后估计那个比例系数——伪偏导数。等我把程序跑通、换到 PFDL、再换到 MIMO 之后才发现单案例演示和体系化理解之间差着十万八千里。这也是我把六套仿真打包在一起的核心原因它们不是六个重复的模板工程而是沿着“从简单到复杂、从单回路到耦合系统”的台阶往上走。1.1 MFAC解决什么问题以及“无模型”到底免掉的是什么传统自适应控制先要建立被控对象的结构方程再在线辨识参数。这套路的问题是很多工业过程根本没有可靠机理模型或者模型阶次不确定辨识参数和控制器设计搅在一起容易顾此失彼。MFAC 的思路完全换了个方向。它不自以为知道对象长什么样子而是利用相邻两个采样时刻的输入输出数据在每一个当前时刻构造一个“局部的动态线性化数据模型”。这里的“无模型”不是没有模型而是不依赖任何先验的物理机理模型。你可以把它类比成一位经验丰富的调试老师傅不需要你画出设备内部原理图他只看你操作了什么、系统响应了什么就能在当前位置附近估计出一个等效增益然后根据这个等效增益决定下一步怎么调整。这套思想落到代码里就是标题中反复出现的两个词CFDL紧格式动态线性化 Compact Form Dynamic Linearization用当前时刻输入增量与输出增量之间一个标量关系来描述局部动态适合大多数 SISO 系统。PFDL偏格式动态线性化 Partial Form Dynamic Linearization把最近 L 个时刻的输入增量拼成一个向量用向量点乘来描述局部动态对付大惯性、时滞、非最小相位系统更稳。有了动态线性化模型MFAC 剩下的任务就是两件事估计伪偏导数然后基于估计结果设计控制器。下面所有仿真案例本质上都在反复练习这套“估计—控制—更新”的闭环。1.2 六套仿真怎么划分更科学我见过有人把六个仿真做成六个不同的被控对象但控制器代码完全一样只是改一改传递函数系数。这种做法不能说没用但对理解 MFAC 帮助不大。我整理时直接按算法难度和系统复杂度划分每一套都解决一个不同的“坎”案例编号被控对象特征动态线性化方式控制目标主要学习点Case 1SISO 无时滞非线性对象CFDL跟踪阶跃与正弦信号理解 PPD 估计与基本控制律Case 2SISO 参数时变 外部扰动CFDL抗干扰与时变跟踪学习重置机制和参数再整定Case 3SISO 大惯性 纯滞后对象PFDL稳准快地跟踪斜坡信号体会 PFDL 在滞后系统中的优势Case 42×2 MIMO 弱耦合对象CFDL-MIMO双通道同时跟踪设定值掌握伪偏导数矩阵估计Case 52×2 MIMO 强耦合对象CFDL-MIMO减弱通道间耦合影响分析耦合通道的非对角元素Case 62×2 MIMO 强耦合 模型参数切换PFDL-MIMO多工况切换下保持稳定综合调试与矩阵运算鲁棒性我可以直说Case 3 和 Case 5 是我自己踩坑最多的两个案例。之前只做 SISO CFDL 时我一度觉得 PFDL 只是多几个参数、没必要用等到了纯滞后加时间常数很大的系统才发现CFDL 的控制器增益会被“等效增益被大惯性拉得很小”这个问题带偏而 PFDL 把最近 L 个时刻的输入变化都纳入预测补偿效果明显改善。如果你模仿这套案例顺序也不要跳步。至少先把 SISO CFDL 跑透再去动 MIMO 矩阵否则你会在矩阵求逆和符号检查上疲于奔命分不清问题是出在控制器还是出在对象。2. 伪偏导数估计与动态线性化仿真代码里最容易出错的地方很多初学代码的人会把伪偏导数估计看成“最小二乘辨识”的变体然后就照着最小二乘公式写。这个直觉有参考价值但 MFAC 的 PPD 估计走的是一步前向梯度校正而且必须配一个不可或缺的重置机制否则仿真跑着跑着就发散。2.1 CFDL与PFDL的数学形式以及PPD估计器的设计逻辑假设一个 SISO 非线性离散系统在相邻两个时刻一定存在一个有界标量 φ(k)使得输出增量可以写成Δy(k1) y(k1) - y(k) φ(k) · Δu(k)这里的 φ(k) 就是 CFDL 中的伪偏导数。它不是系统真实梯度而是一个在“当前工作点附近”把非线性映射等价成线性关系的时变参数。因为系统是时变的φ(k) 也被允许随时间变化。这个形式不需要对系统阶次作任何假定所以它对一大类非线性系统都成立。为了估计 φ(k)MFAC 的经典做法是极小化如下指标J [Δy(k) - φ_hat(k) · Δu(k-1)]² μ · [φ_hat(k) - φ_hat(k-1)]²第一项让估计误差变小第二项把 φ_hat 的变化速度限制住防止它跳得太厉害。μ 就是这个“变化惩罚”的权重。对上述准则求极值后得到递推估计式φ_hat(k) φ_hat(k-1) [η · Δu(k-1) / (μ Δu(k-1)²)] · [Δy(k) - φ_hat(k-1) · Δu(k-1)]这里 η 是步长因子。分母中保留 Δu(k-1)² 项是为了解决“当输入增量很小时估计更新幅度过大”的问题。你可以理解为如果上一次操作基本没动那么这次观察到的输出变化很可能来自噪声或扰动不应该让 φ_hat 产生剧变。PFDL 的推导是 CFDL 的自然延伸。它对长度为 L 的输入增量窗口建模Δy(k1) φ1(k)Δu(k) φ2(k)Δu(k-1) … φ_L(k)Δu(k-L1)写成向量形式就是伪梯度向量 φ_{n,L}(k) 与输入增量向量 ΔU_L(k) 的内积。其估计式形式类似只是分母中的标量平方变成向量模平方φ_hat(k) φ_hat(k-1) [η · ΔU_L(k-1) / (μ ||ΔU_L(k-1)||²)] · [Δy(k) - φ_hat(k-1)^T · ΔU_L(k-1)]在代码里我建议把 CFDL 和 PFDL 的估计封装成两个独立函数而不是在同一函数里用 if 判断。因为两者的数据缓冲结构不同CFDL 只需要上一拍输入增量PFDL 需要维护一个长度 L 的队列。封装分开后面做 MIMO 扩展时也不容易乱。2.2 仿真中PPD初始化、重置条件和参数μ/η/λ的调节顺序PPD 估计器有三个点最容易被代码新手忽略。第一个是初值 φ_hat(1)。这个初值不能设成 0。原因很简单控制律中要用 φ_hat 计算控制增益如果初值为 0第一拍根本算不出有效控制量。我习惯根据被控对象正常工作点处的“粗略增益”来设初值。比如对象输入是 0 到 1输出是 0 到 10初值我会给 5 到 15 之间的一个数。如果你完全没有任何先验就给一个正的常数比如 1也比 0 好。第二个是重置机制。递推估计算法本身不能保证 φ_hat 永远合理。当出现以下情况之一时需要把 φ_hat 重置为初值|Δu(k-1)| 小于某个阈值 ε此时估计信息太少|φ_hat(k)| 小于某个阈值 ε说明估计值失去意义φ_hat(k) 的符号发生突变比如从正变成负且与上一拍符号不一致。这个重置机制非常像经验丰富的操作员发现仪表读数可疑后重新归零校准。没有它PPD 估计很容易在一两次异常扰动后跑飞后续的控制量也会跟着剧烈振荡。第三个是参数调节顺序。建议的优先级是先调控制器权重 λ再调重置阈值 ε然后调估计步长 η 和惩罚系数 μ最后调控制律步长 ρ。λ 在主对角线上相当于控制输入的“软化系数”λ 越大控制量变化越平缓但跟踪可能变慢。η 是估计器步长太大会让 PPD 估计抖动太小会让系统没法跟上参数时变。μ 是估计器变化惩罚太大会让 PPD 僵化太小会导致估计器过于敏感。ρ 是控制器步长通常取 0 到 1 之间用来折中跟踪速度和控制能量。我自己的经验是对于一台“看起来正常”的被控对象起始参数可以设 η1μ1λ2ρ0.6然后再根据响应曲线微调。如果你一上来就遇到发散第一反应应该去调 λ而不是死磕 ρ。λ 稍微调大一点往往就能让系统安静下来。3. 六个仿真实例逐个拆解对象、参数与输出对比这一节我把六套仿真程序的被控对象形式、控制器配置和典型结果都列出来。你完全可以用相同配置去复现数值上小有差异是正常的整体趋势应该一致。3.1 SISO CFDL/PFDL案例的实现与分析Case 1 我故意选了一个带有非线性平方项的对象y(k1) 0.8·y(k)/(1y(k)²) 0.6·u(k) 0.2·sin(y(k)·u(k))采样周期取 0.01 秒仿真 2000 步。CFDL 控制器参数我取 η1μ0.8λ2.5ρ0.6φ 初值取 0.8。跟踪信号先给阶跃 1.0在第 800 步切换为幅值 0.5、频率 0.01 的正弦信号。结果上阶跃跟踪基本没有超调调整时间约 60 步切换到正弦后相位滞后很小最大绝对跟踪误差在 0.05 以内。Case 2 在对象里加入了时变增益和扰动比如在 600 步时让原对象增益从 0.6 变成 1.1并在第 400 到 450 步叠加一个幅值 0.3 的阶跃扰动。此时保持 Case 1 的参数不换你会发现 PPD 估计值会在增益突变后短暂波动系统约 80 步后重新收敛。这正好说明 MFAC 的自适应能力不是来自模型在线辨识而是来自 PPD 的在线校正。Case 3 换成了一个带纯滞后的大惯性环节我把它写成差分方程形式y(k1) 0.92·y(k) 0.08·u(k-10)这是一个典型的一阶惯性加滞后系统滞后 10 拍。如果继续用 CFDL控制器常常会显得“迟钝”因为当前输出变化反映的是 10 拍之前的控制作用。改用 PFDL 之后我取 L8把最近 8 拍的输入增量都用于估计控制效果明显改善。这里我想特别提醒L 不是越大越好。L 大了确实能覆盖更长滞后但需要估计的参数变多PPD 向量更新的计算量增大且对噪声更敏感。L 取滞后拍数附近是比较合理的经验值。3.2 MIMO CFDL/PFDL案例以及如何看耦合补偿效果Case 4 和 Case 5 都是 2×2 系统我使用了一组带非线性耦合的差分方程它的结构在资料里作为“通用两入两出对象函数”提供核心形式是y1(k1) 0.9·y1(k)/(1y1(k)²) 0.5·u1(k) c·u2(k)·sin(y1(k)) y2(k1) 0.9·y2(k)/(1y2(k)²) 0.5·u2(k) c·u1(k)·cos(y2(k))其中 c 代表耦合强度。Case 4 里取 c0.1Case 5 里取 c0.4。这样两者的差别就只在于耦合强度便于对比 MIMO MFAC 的表现。MIMO 用 CFDL 时伪偏导数从标量变成矩阵 Φ(k)ΔY(k1)Φ(k)ΔU(k)。Φ(k) 的对角元素反映各通道自身增益非对角元素反映耦合通道的影响。控制律变成U(k) U(k-1) ρ·Φ(k)^T·[λ·I Φ(k)·Φ(k)^T]^{-1}·[Y*(k1) - Y(k)]这里面最容易让人不安的就是矩阵求逆。不过只要 λ 取得合适λI ΦΦ^T 是对称正定矩阵求逆是安全的。我在 Case 4 里取 λ1.5ρ0.5φ 对角初值取 0.6非对角初值取 0。两个通道的设定值分别是 1.0 和 2.0。刚开始时第二通道会出现一个小的耦合波动幅度约为设定值的 10% 左右控制作用很快把它压回去了。Case 5 把耦合系数提高到 0.4 后如果继续用 Case 4 的固定参数系统的耦合波动会变大但 MFAC 矩阵中的非对角元素并不自动“解耦”它只是把耦合的影响当成对象动态的一部分通过 PPD 矩阵去实时补偿。这句话要画重点MFAC 没有显式解耦器它的“解耦”是隐式的、数据驱动的。Case 6 则是在 Case 5 基础上每隔 500 步切换一次对象参数并换成 PFDL-MIMO 形式主要为了观察多工况下矩阵估计是否能跟上变化。4. MIMO MFAC仿真的耦合问题伪偏导数矩阵里该放什么很多同学第一次接触 MIMO MFAC 时脑子里想的都是“能不能先分别控制每一个回路把耦合通道忽略掉”。如果耦合很弱这么做并无不可但耦合强时被忽略的通道间影响会在两个回路上形成正反馈振荡。所以 MIMO 仿真真正要解决的问题不是理论上的显式解耦而是“如何让控制器及时发现耦合并把影响吃掉”。4.1 MIMO控制律的矩阵运算与稳定性/可行性条件MIMO CFDL 中伪偏导数矩阵 Φ(k) 的估计是一个矩阵更新而不是对每个元素独立地套用 SISO 公式。教科书里常见的方式是把矩阵“向量化”然后用等效的递推公式逐行更新。实际编程时更推荐直接对每一行做标量式更新。因为第 i 个输出通道对应的伪偏导数行向量 φ_row_i(k) 满足Δy_i(k1) φ_row_i(k)^T · ΔU(k)所以我可以把 SISO 的估计式直接推广到每一行φ_hat_row_i(k) φ_hat_row_i(k-1) [η·ΔU(k-1) / (μ ||ΔU(k-1)||²)] · [Δy_i(k) - φ_hat_row_i(k-1)^T·ΔU(k-1)]这样写代码结构最接近 SISO 版本也最容易检查。控制律中的矩阵求逆 (λI ΦΦ^T)^(-1)本质上是在输入变化量上做一个非线性的加权投影。ΦΦ^T 半正定λI 确保求和后正定所以求逆这个动作本身是稳定的。但如果你把 λ 设得太小比如 λ1e-6矩阵条件数会非常大仿真中可能出现“矩阵接近奇异”的警告。这时不要只是调 λ先停下来看看 PPD 矩阵的范数是不是已经发散到一个非常夸张的量级。如果 PPD 矩阵发散多半是估计步长 η 太大或者 μ 太小先修正估计器再回来看矩阵。4.2 耦合强不强怎么在仿真中量化以及什么时候该从MIMO退回多回路仿真实战里耦合强度不只是一个理论参数。我会在实际调试时做一个小实验把其中一个控制通道的输入在前 200 步保持恒定另外一个通道输入加一个阶跃然后观察另一个输出的变化幅度。变化越大说明耦合越强。用这个简单方法你可以很快判断当前工作点附近的耦合强度而不是只看整组方程里的系数 c。耦合强度判定之后决定用 MIMO-MFAC 还是多回路独立 MFAC我会按下面这个经验规则耦合情况通道间影响推荐方案弱耦合小于本通道增益的 10%多回路独立 MFAC参数更简单中等耦合10% ~ 30%MIMO MFAC但可容忍初始耦合波动强耦合大于 30%必须用 MIMO MFAC且注意 PPD 矩阵非对角项的估计质量需要提醒的是MIMO MFAC 也不是万能的。当系统存在输入输出通道数不一致或者工作点附近增益符号频繁翻转时矩阵形式的 PPD 估计会变得很吃力。此时与其强行用一个 MIMO MFAC不如在工程上考虑串级、前馈这些更成熟的架构。5. 程序框架与调试经验跑通只是第一步这六套仿真程序的力量不在于“能跑”而在于“改起来方便”。如果每一套都是把全部代码堆在一个几百行的脚本里后续替换对象、切换算法、加扰动都会很难受。所以我按工程化的方式组织了目录和接口。5.1 工程目录、接口与仿真流程目录结构大致如下mfac_sim/ ├── README.md # 说明每套案例的用途和文件入口 ├── config/ │ └── default_params.m # 统一参数结构体 ├── plants/ │ ├── plant_siso_nonlinear.m │ ├── plant_siso_lag.m │ └── plant_mimo_coupled.m ├── controllers/ │ ├── mfac_cfdl.m │ ├── mfac_pfdl.m │ ├── mfac_mimo_cfdl.m │ └── mfac_mimo_pfdl.m ├── estimators/ │ ├── ppd_estimate_cfdl.m │ ├── ppd_estimate_pfdl.m │ └── ppd_matrix_estimate.m └── scripts/ ├── run_case1.m ├── run_case2.m └── ...每个主脚本的执行流程都是同一个结构读取参数结构体设置采样步数、设定值、扰动区间初始化状态变量、PPD 初值、历史输入输出缓冲进入仿真循环每个采样时刻先由对象函数算出当前输出基于当前输出与设定值调用 PPD 估计函数更新伪偏导数调用控制器函数计算控制增量并执行输入饱和检查记录数据绘制响应曲线。这套流程最大的好处是换对象、换算法时你只需要改初始化和函数调用不用动主循环。比如从 CFDL 切到 PFDL核心改动就是估计器多维护一个长度为 L 的历史向量以及控制律从标量增益变成向量内积。这正好呼应了前文说的“理解台阶”思路。5.2 调试中常见的五个坑和排错链路我整理资料时把调试过程中最常踩的坑连同排错链路一起写进了 README。这里挑五个最容易复现的症状一第一拍控制量为 NaN。排查链路先检查 PPD 初值是否为零再检查 λIΦΦ^T 求逆结果最后检查对象输出是否有除零项。症状二输出发散曲线呈“锯齿状”快速振荡。优先怀疑控制律 ρ 太大或 λ 太小先调 λ再看 η。症状三输出收敛但存在稳态静差。先看 PPD 估计是否有重置再检查控制器是否因为 λ 过大导致积分效果太弱。MFAC 本身是增量式控制器有类似积分的功能如果 λ 过大静差就会拖长。症状四PPD 估计值在短时间内从正跳到负。这是符号突变通常是重置机制没有生效或者对象增益符号在当前工作点附近确实发生了翻转。前者修代码后者建议改成多工作点分段处理。症状五MIMO 仿真时出现 “Matrix is singular to working precision”。别急着加 λ先打印 ΦΦ^T 的秩如果秩小于输入维数说明某个通道的 PPD 估计向量退化回到估计式检查 μ 和 η。每个坑我都建议在代码里加一个“诊断开关”可以在仿真中途把 PPD、控制量、输出误差画在同一个图里。不要只画输出曲线否则你看到发散时根本不知道是估计器先坏还是控制器先坏。6. 配套参考资料的使用顺序与下一步扩展思路这套资料包里不仅有六个仿真工程还有一批按阅读顺序整理的文档。我不建议你把资料 PDF 全部塞进网盘就完事然后从头到尾翻一遍。文本阅读顺序和代码调试顺序是两回事。6.1 我整理资料时的阅读顺序建议第一梯队是“入门综述”先看 MFAC 的整体框架、与传统自适应控制的区别、动态线性化为什么成立。这一部分不需要看懂每一个证明只需要建立“估计 PPD—计算控制律—重置校正”的闭环直觉。第二梯队是“算法推导”重点看 CFDL 和 PFDL 的详细推导、PPD 估计准则的构造、稳定性分析的基本假设。读这一梯队时建议对照本包里的estimators目录每读一个公式就在代码里找到对应的一行。这样可以把抽象符号落到具体变量上记忆效果远好于单纯看公式。第三梯队是“MIMO 与扩展”这部分内容是给已经跑通 SISO 仿真的人看的重点在 PPD 矩阵的结构、控制律矩阵求逆的几何意义、输入约束与时滞系统的扩展方法。读之前最好先跑一遍 Case 4 和 Case 5这样你再看矩阵公式就不会觉得它是一堆冰冷的符号。第四梯队才是“代码逐行注释”和“常见问题 FAQ”。很多初学者一上来就抱着代码逐行啃啃到第 50 行就忘了系统全貌。我建议先读文档建立系统观再回到代码里看细节效率会高得多。6.2 从仿真到实际应用还能做哪些扩展做完这六套仿真之后你可以沿着几个方向继续延伸。第一个方向是输入约束和输出约束。实际系统中控制量不可能无限大把 MFAC 控制律的输出加一个饱和限幅是很容易的事但限幅之后 PPD 估计是否还能保持稳定需要做额外测试。我自己会用“约束激活计数器”去观察控制量在限幅边界上停留的时间如果长时间抱死就要考虑抗饱和处理。第二个方向是时滞系统的进一步补偿。PFDL 用滑动窗口缓解了滞后影响但如果滞后很大而且时变简单的固定长度窗口就不够聪明。可以尝试把系统识别出的纯滞后步数与 PFDL 的窗口长度联动滞后变大时自动增大 L。第三个方向是控制器结构的混合。比如把 MFAC 作为主回路外面套一个 PID 或前馈回路用于抑制可预知的扰动。这种混合结构在工业现场很常见因为现场工程师习惯保留一个“可解释”的基础控制回路MFAC 作为校正回路只需要在线修正增量不会让人觉得黑箱不可控。最后一点个人体会做 MFAC 仿真最大的收获不是“把代码跑通了”而是形成了“数据—模型—控制”三者循环修正的直觉。即使是同一条曲线今天跑是平滑的明天换一组初值可能就出现奇怪振荡这不代表算法有问题而是说明你对系统动态的理解还不够。仿真阶段多设几个“病态工况”比永远在理想曲线上满意更有价值。按这套程序包 drill 下来再回去读任何与无模型自适应控制相关的论文你应该能很快判断出它宣称的改进点到底落在哪一环是 PPD 估计更鲁棒还是控制律更平滑还是 MIMO 耦合处理更聪明。有了这个判断力后续做创新也就不再是无从下手。