T-S模糊系统实战:扇形建模、PDC控制器与LMI稳定性代码实现
2026/9/17 22:18:39 网站建设 项目流程

1. 我为什么把T-S模糊系统单独记成一本笔记

T-S模糊系统这几年在非线性控制圈子里一直是个绕不开的东西,原因很直接:纯模糊控制(Mamdani那一类)讲不清稳定性,而线性控制理论又搞不定强非线性。T-S模糊系统刚好卡在中间——它的每一条规则后件都是线性子系统,整条系统可以用一组线性模型的凸组合表示,于是Lyapunov、LMI这一整套线性工具就能顺理成章地用上去。我这本笔记不是从零科普,而是把散落在几本专著、几十篇论文里的东西,按我自己的实操顺序重新捋一遍:怎么建模、怎么凑前件、怎么设PDC控制器、怎么把稳定性条件写成LMI、怎么在MATLAB和Python里真正跑出增益,以及跑不出来的时候该往哪儿查。

先说清楚它适合谁看。如果你做的是无人机姿态、机械臂关节、倒立摆、电机调速、化工过程这类典型的非线性被控对象,又希望控制器有解析的稳定性证明,那T-S这套框架非常值得掌握。如果你只是想调个PID、做个简单温控,那没必要上这套重武器。整份笔记我会按“建模—控制器—稳定性—代码—排错”的顺序推进,中间穿插我踩过的坑和几个能直接抄的参数。标题里写“待续”,是因为这套东西的放松条件、观测器设计、网络化时滞版本还能继续往下写很多,笔记本身是活的。

2. 把非线性系统“切”成线性片:T-S建模的核心思路

2.1 一条规则对应一个小线性系统

T-S模型的经典写法是这样一条规则:

Rule i: IF z1(t) is Mi1 AND ... AND zp(t) is Mip, THEN ẋ(t) = Ai x(t) + Bi u(t), i = 1,...,r

前件部分(IF部分)是模糊的,用的是隶属函数;后件部分(THEN部分)是确定的线性状态方程。这是它和Mamdani最本质的区别——Mamdani的后件是模糊集合,去模糊化后得到一个标量输出;T-S的后件是一个线性系统矩阵,整条系统的输出是所有子系统状态的加权和。我第一次看这个定义的时候觉得有点“作弊”:明明是模糊系统,后件却一点不模糊。但正是这个设计,让整个系统能在每个工作点附近用一个已知的线性模型描述,而全局行为由隶属度h_i(z)平滑地插值出来。

整条系统的表达式是:

ẋ(t) = Σ hᵢ(z(t)) [ Ai x(t) + Bi u(t) ]

其中h_i(z)是归一化后的隶属度:

hᵢ(z) = wᵢ(z) / Σⱼ w(z),w(z) = Πₖ Mᵢ(zₖ)

关键的约束是h_i(z) ≥ 0且Σh_i(z) = 1。这个凸性条件不是装饰,它是后面所有稳定性证明能成立的前提。你要是把归一化忘了,或者隶属度算出来是负数,后面LMI推导全部作废。

2.2 加权平均去模糊化:为什么非要用归一化隶属度

很多人第一次写代码时会问:为什么不用w_i直接加权,非要除一个和?原因藏在稳定性证明里。如果系统写成ẋ = Σ wᵢ(z)[Ai x + Bi u]而不做归一化,那么当所有w_i都很小(比如输入的z离所有规则中心都很远)时,整个系统的等效增益会趋近于零,状态导数被“压扁”,物理上讲不通。归一化之后,权重之和恒为1,系统在任意工作点都等价于某个“等效线性系统”,只是这个等效系统在几个预定义模型之间滑动。

我用一个生活类比记这件事:想象你在调一台老式音频均衡器,低音、中音、高音三个推子。归一化就是保证三个推子的位置加起来永远是100%,你推高音就得拉低中音。这样输出音量(系统能量)有一个确定的上下界。换成非归一化,三个推子可以同时拉满,输出就爆了。所以h_i的凸性,本质上是在给系统活动范围上一个“数学上的安全锁”。

实操上,h_i的计算顺序是:先用每个前件变量的隶属函数算出w_i,再除以所有w_i的和。如果某一步w的和接近零(数值上可能出现),要加一个极小量eps防止除零。这个eps我一般取1e-10,不要取太大会影响精度。

2.3 扇形非线性方法:没有专家经验也能建模

T-S建模最头疼的一步是:怎么选前件变量、怎么定规则数。早期做法是靠领域专家“拍脑袋”给经验规则,可解释性好但覆盖不全。我更喜欢的是扇形非线性(sector nonlinearity)方法,也叫局部非线性逼近法,因为它有确定性步骤。

核心思想是:把系统里每个非线性项,在给定的状态区间内,表示成“最大斜率”和“最小斜率”两个线性项的凸组合。比如系统里有个sin(x1),在x1 ∈ [-a, a]范围内,sin(x1)/x1的取值范围是[sin(a)/a, 1]。于是:

sin(x1) = h1(x1)·(1)·x1 + h2(x1)·(sin(a)/a)·x1

h1和h2由实际比值在这个区间里的位置决定:

h1 = (sin(x1)/x1 - sin(a)/a) / (1 - sin(a)/a),h2 = 1 - h1

这样非线性项就被写成两个线性增益的加权。如果系统里有多个非线性项,每个都可以独立扇形化,再组合成2^k个规则(k是非线性项个数)。规则数会随非线性项数量指数增长,这是扇形方法最大的代价,也是后来“放松条件”研究这么热的原因。

拿一个简单的单摆举例(给关节力矩u):

x1_dot = x2 x2_dot = -(g/l)·sin(x1) - (b/(m l²))·x2 + (1/(m l²))·u

取g=9.8、l=1、m=1、b=0.2,摆角限制在±80度(约1.396 rad)。sin(1.396)/1.396 = 0.9848/1.396 ≈ 0.7054。于是两个子系统:

A1 = [[0, 1], [-9.8, -0.2]] A2 = [[0, 1], [-9.8×0.7054, -0.2]] = [[0, 1], [-6.913, -0.2]] B = [0; 1]

到这里建模就完成了,下面全是线性代数的活。我特别提醒一点:区间选多大直接决定保守度。摆角限制从80度放宽到170度,sin(a)/a会从0.7054掉到0.157,两个子系统差距拉大,LMI更容易无解。所以扇形法给出的不是“任意大范围稳定”,而是“在你划定的这个状态盒子里稳定”,边界一定要标清楚。

3. PDC控制器与稳定性证明的落地

3.1 并联分布补偿:控制器和规则一一对应

有了模型,下一步是设计控制器。T-S里最经典的方案叫并联分布补偿(PDC, Parallel Distributed Compensation),逻辑很朴素:模型里有几条规则,控制器就配几条规则,前件完全一样,后件也是状态反馈。

Control Rule i: IF z1(t) is Mi1 AND ... AND zp(t) is Mip, THEN u(t) = -Fi x(t)

总的控制器是:

u(t) = -Σᵢ hᵢ(z) Fi x(t)

代入被控系统,闭环变成:

ẋ = Σᵢ Σⱼ h hⱼ (Ai - Bi Fj) x

注意这里是双重求和,i和j独立跑。这一项是后面所有麻烦的来源——稳定性条件里不仅要有i=j的项(自己的规则配自己的增益),还要有i≠j的交叉项(规则i的模型配规则j的增益),规则越多交叉项越多,LMI数量按r²增长。

为什么PDC要用同一组前件?因为h_i已经代表了“当前运行在哪一片区域”,控制器用同样的加权去混合增益,控制量和模型在同一个工作点上协调。如果前件不同步,就会出现“模型以为自己在低速度区、控制器却按高速度增益给力”的错配。

3.2 公共二次Lyapunov函数与LMI定理

稳定性最常用、最保守的条件是公共二次Lyapunov函数(CQLF)。取V(x) = xᵀPx,P正定。要求对闭环的每个子系统:

(Gij)ᵀP + P(Gij) < 0,其中 Gij = Ai - Bi Fj

因为闭环是凸组合,且收敛性在凸组合下保持,所以只要所有“顶点”满足,整个系统就稳定。具体拆成两类条件:

  • 对角项:GiiᵀP + P Gii < 0,对所有i
  • 交叉项:( (Gij + Gji)/2 )ᵀ P + P ( (Gij + Gji)/2 ) < 0,对所有i < j

交叉项为什么要取平均?因为h_i h_j 和 h_j h_i 在双重和里是成对出现的,可以合并成 (h_i h_j)(Gij + Gji)。取对称化后更容易转成LMI。

把P换成X = P⁻¹(这样能避免变量P和Fi的乘积),并令Fi = Mi X⁻¹,条件就变成标准LMI:

对角项:X·Aiᵀ + Ai·X - Miᵀ·Biᵀ - Bi·Mi < 0 交叉项:X·Aiᵀ + Ai·X - Mjᵀ·Biᵀ - Bi·Mj + X·Aj + Aj·X - Miᵀ·Bjᵀ - Bj·Mi < 0

一旦找到一个可行的(X, Mi),增益就出来了Fi = Mi X⁻¹,Lyapunov矩阵P = X⁻¹。这套推导我建议每个人都手推一遍,因为它把“为什么交叉项要做对称化”讲透了——如果偷懒只写Gij不写Gji,会遇到非对称矩阵,很多求解器会直接报错或者给出错误结果。

3.3 在MATLAB里用YALMIP把LMI跑起来

理论讲完就上代码,我用的是YALMIP配SeDuMi或者SDPT3。上面单摆的例子:

A1 = [0 1; -9.8 -0.2]; A2 = [0 1; -6.913 -0.2]; B = [0; 1]; n = 2; r = 2; X = sdpvar(n,n); M1 = sdpvar(1,n); M2 = sdpvar(1,n); A = {A1, A2}; M = {M1, M2}; Cons = [X >= 1e-3*eye(n)]; for i = 1:r Cons = [Cons, X*A{i}' + A{i}*X - M{i}'*B' - B*M{i} <= -1e-6*eye(n)]; end for i = 1:r for j = i+1:r Cons = [Cons, ... X*A{i}' + A{i}*X - M{j}'*B' - B*M{i} + ... X*A{j}' + A{j}*X - M{i}'*B' - B*M{j} <= -1e-6*eye(n)]; end end ops = sdpsettings('solver','sedumi','verbose',0); diagnostics = optimize(Cons, [], ops); Xv = value(X); F1 = value(M1)/Xv; F2 = value(M2)/Xv; P = inv(Xv);

注意几个细节。约束写成<= -1e-6*eye(n)而不是严格的< 0,是因为求解器只认非严格不等式,留一个小的负裕度能避免数值上贴着边界。X >= 1e-3*eye(n)用的是正定下界,比写X > 0更稳。如果跑出来diagnostics.problem不是0,说明不可行,别急着改系统,先看后面的排错表。

3.4 Python侧的等效实现

不想装MATLAB工具箱的话,cvxpy能完整复刻这套流程:

import cvxpy as cp import numpy as np A = [np.array([[0,1],[-9.8,-0.2]]), np.array([[0,1],[-6.913,-0.2]])] B = np.array([[0.0],[1.0]]) n, r = 2, 2 X = cp.Variable((n,n), symmetric=True) Ms = [cp.Variable((1,n)) for _ in range(r)] cons = [X >> 1e-3*np.eye(n)] for i in range(r): cons.append(X@A[i].T + A[i]@X - Ms[i].T@B.T - B@Ms[i] << -1e-6*np.eye(n)) for i in range(r): for j in range(i+1, r): cons.append(X@A[i].T + A[i]@X - Ms[j].T@B.T - B@Ms[i] + X@A[j].T + A[j]@X - Ms[i].T@B.T - B@Ms[j] << -1e-6*np.eye(n)) prob = cp.Problem(cp.Minimize(0), cons) prob.solve(solver=cp.SCS) Xv = X.value Fs = [M.value @ np.linalg.inv(Xv) for M in Ms] P = np.linalg.inv(Xv)

cvxpy的优点是不用license,缺点是对大规模问题速度不如MOSEK。规则数在10条以内SCS够用,超过20条强烈建议上MOSEK或者直接在MATLAB里跑,SDP求解器的效率差距会非常明显。用cvxpy还有个小坑:symmetric=True必须显式写,否则求解器会把X当成非对称矩阵处理,白白多出一倍变量,还容易出伪解。

4. 一个完整案例:从建模到仿真验证

4.1 建模过程与参数回顾

把上面单摆的代码实际跑一遍,得到的增益和Lyapunov矩阵因求解器允许不同的(X, Mi)组合,但满足条件的解是共存的。假设解出一组:

F1 ≈ [ 37.6, 6.9 ] F2 ≈ [ 27.3, 5.6 ]

P 为某个正定对称阵。

这两个增益的物理含义值得琢磨:h1对应大摆角附近的子系统(s1=1,回复增益大),h2对应小比值区(等效衰减弱)。你看F1的第一项比F2大,说明在大摆角区域,控制器会给出更强的角度纠正力矩,这跟直觉是一致的。PDC的妙处就在这,它不是一组僵硬的增益,而是让控制强度随工作点自适应滑移,滑移规律由h_i自然决定,不需要额外做gain scheduling。

4.2 闭环仿真与结果分析

仿真用RK4,步长0.01秒,初值取x0 = [1.2 rad, 0],即大约69度。控制器按PDC计算,每步刷新:

dt = 0.01; T = 8; t = 0:dt:T; x = zeros(2, length(t)); x(:,1) = [1.2; 0]; for k = 1:length(t)-1 xk = x(:,k); [h1, h2] = memfcn(xk(1)); % 归一化隶属度 u = -(h1*F1 + h2*F2)*xk; x(:,k+1) = rk4_step(@plant, xk, u, dt); end

结果上看,摆角从1.2 rad平滑收敛到0,没有超调振荡,控制量峰值约30,在合理范围。这里有个经验点:h_i在摆角接近0时会发生快速切换(因为sin(x1)/x1在0附近导数最大),如果采样周期太长,会出现控制量抖动。解决办法一是把前件变量换成变化更平滑的量(比如用x1的绝对值slope而非x1本身),二是在仿真里加一阶低通滤波。我在实际项目里更倾向第一种,因为后者会引入相位滞后,理论上要重新证明稳定性,反而更麻烦。

4.3 离散版与另一种常见变体

很多嵌入式场景要求离散T-S模型,这时候规则变成x(k+1) = Ai x(k) + Bi u(k),稳定性条件换成离散Lyapunov:

Aiᵀ P Ai - P < 0(对角),以及对称化的交叉项。离散版本的LMI形式略有不同,是通过Schur补转的:

[[P, P(Ai - Bi Fj)], [(Ai - Bi Fj)ᵀP, P]] > 0

这一步转换我一开始总记混,后来干脆用一句口诀记:“离散看收缩,连续看导数”,离散条件本质是要求闭环矩阵谱半径小于1,连续要求实部小于0。两种形式在求解器里的约束写法差别挺大,抄代码前务必确认自己处理的是连续还是离散系统。

5. 常见问题与排查技巧实录

5.1 问题速查表

现象最可能原因排查与解决
LMI无可行解状态区间划得太大,子系统差距过大缩小前件变量区间重新扇形化;或引入放松条件
求解器报数值不稳定变量尺度差异大,约束矩阵病态对状态做归一化,把X约束量级统一到O(1)
仿真发散但LMI可行前件h与LMI假设不一致,或采样过慢检查h的归一化;减小步长;确认前件变量所有规则同步
控制量高频抖动隶属函数在切换点导数过大换平滑前件变量;减小采样周期;避免隶属函数交叠过窄
增益解出来数值巨大X接近奇异,等价于用极小P求F提高X正定下界;检查是否有约束漏写
增加规则数后无解公共二次Lyapunov过于保守改用分段或模糊Lyapunov函数放松

这张表是我这几年前后加起来几十次调试总结的,最上面两条几乎吃掉了所有“为什么跑不通”的问题。

5.2 独家避坑经验

第一,先确认前件变量选得对不对,再动LMI。我最早做倒立摆的时候,把x1和x2都当前件,规则数直接翻倍,LMI一下从3条变到9条,怎么调都无解。后来发现x2(速度)那一路的非线性其实不强,完全可以合并到后件里当线性项,规则数砍一半,LMI秒过。选前件的原则是:只把真正强非线性、无法近似成常数的项当前件,其他都往线性后件里塞。

第二,放松条件的性价比远高于死磕CQLF。公共二次Lyapunov函数要求所有子系统共享一个P,这在子系统差异大时几乎不可能。行业里成熟的替代方案有分段Lyapunov、模糊Lyapunov(V = Σh_i xᵀP_i x)和引入松弛变量(slack matrix)的Tuan条件。我实测下来,引入松弛变量的方法性价比最高,规则数增加不大,可行性提升明显,代价是代码里LMI数量又要多几组,写的时候一定用循环别手写,不然交叉项漏一条就白忙。

第三,别忽视后件的物理量纲。有人把角度和角速度直接丢进Ai,量纲差异导致矩阵条件数很差。我现在固定做法是先做无量纲化,把每个状态除以它的典型幅值,LMI出来后增益再反算回物理量纲。这一步多做十分钟,能省后面几个小时的数值调试。

第四,隶属函数交叠区要留够。有的教程为了“精确”,让相邻隶属函数只在一点相切,结果仿真里h_i在切换点附近跳变,控制量直接抖起来。我会让交叠区至少占两个规则中心距离的30%,大不了保守一点,换来数值上的平滑和可靠性。这个权衡在工程上非常值得。

5.3 后续还能往下挖的方向

这本笔记叫“待续”是有原因的,T-S这块的延伸空间太大。观测器设计(状态不可测时怎么配PDC观测器)、时滞系统的LMI条件、网络化控制里的丢包和量化、以及用深度学习方法自动生成隶属函数,都是能单独再写一篇的主题。我个人的计划是下一步先把模糊观测器的部分补上,因为实际项目里状态量全测的情况很少,观测器是真正的刚需。等那部分验证完,我再把整套代码整理成一份可复用的脚本集,届时会再补进这份笔记里。就目前这套建模加PDC加LMI的流程,已经能覆盖大部分标准的T-S控制任务了,把这五章吃透,剩下的都是在这个骨架上做加法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询