4. 模糊PID整定
任务目标
针对含死区与饱和非线性的被控对象,设计一个二维模糊PID控制器(输入为误差e与误差变化率ec,输出为ΔKp、ΔKi、ΔKd),通过模糊规则在线调整PID参数,改善阶跃响应的快速性与稳态精度。本案例使用Python实现完整仿真闭环,不依赖外部仿真工具。
被控对象与难点
被控对象传递函数为:
G(s) = 12 / (s² + 2.5s + 4)
串联以下非线性环节:
- 死区:|u| < 0.3时输出为0(不动作)
- 饱和:|u| ≥ 3.0时输出限幅为±3.0
死区导致小误差时控制量被吞掉,产生稳态误差;饱和限制大误差时的控制力度,影响响应速度。固定PID难以同时兼顾两者,模糊PID根据误差状态动态调整参数。
模糊PID设计
采用增量式模糊调整策略:
Kp = Kp0 + ΔKp
Ki = Ki0 + ΔKi
Kd = Kd0 + ΔKd
其中Kp0=2.5,Ki0=0.8,Kd0=0.4为固定初值。模糊控制器输入e与ec的论域均为[-1, 1],输出ΔKp论域[-0.6, 0.6],ΔKi论域[-0.3, 0.3],ΔKd论域[-0.2, 0.2]。隶属函数采用三角形,语言变量为{NB, NS, ZO, PS, PB}共5档。模糊规则库共25条,核心规则如下:
- 当|e|大时,增大Kp加快响应,减小Ki防止超调;
- 当e与ec同号(误差增大)时,增大Kd增强阻尼;
- 当|e|小且|ec|小时,增大Ki消除稳态误差。
完整实现代码
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# ---------- 模糊隶属函数 ----------
def tri(x, a, b, c):
"""三角形隶属函数"""
if x <= a or x >= c:
return 0.0
if x == b:
return 1.0
if x < b:
return (x - a) / (b - a)
return (c - x) / (c - b)
# 输入论域:e和ec均为[-1, 1],5个语言值
e_range = [-1.0, -0.5, 0.0, 0.5, 1.0] # NB, NS, ZO, PS, PB 的中心点
ec_range = [-1.0, -0.5, 0.0, 0.5, 1.0]
# 输出论域中心点
dkp_range = [-0.6, -0.3, 0.0, 0.3, 0.6]
dki_range = [-0.3, -0.15, 0.0, 0.15, 0.3]
dkd_range = [-0.2, -0.1, 0.0, 0.1, 0.2]
# ---------- 模糊规则表(25条)----------
# 规则表索引:rule[i][j] = [ΔKp档位, ΔKi档位, ΔKd档位]
# i为e的档位(0=NB,...,4=PB),j为ec的档位
rule = [
# ec: NB NS ZO PS PB
[[2, 0, 4], [2, 0, 3], [3, 0, 2], [3, 0, 2], [4, 0, 2]], # e=NB
[[2, 1, 3], [2, 1, 3], [3, 1, 2], [3, 1, 2], [4, 1, 1]], # e=NS
[[2, 2, 2], [2, 2, 2], [3, 2, 2], [2, 2, 2], [2, 2, 2]], # e=ZO
[[1, 3, 1], [1, 3, 1], [1, 3, 2], [2, 3, 2], [2, 3, 3]], # e=PS
[[0, 4, 0], [0, 4, 1], [1, 4, 2], [1, 4, 3], [2, 4, 4]], # e=PB
]
# 档位含义:0=NB, 1=NS, 2=ZO, 3=PS, 4=PB
def fuzzy_infer(e, ec):
"""模糊推理,返回ΔKp, ΔKi, ΔKd"""
# 计算e和ec在各档位的隶属度
mu_e = [tri(e, e_range[max(0, i-1)], e_range[i], e_range[min(4, i+1)]) for i in range(5)]
mu_ec = [tri(ec, ec_range[max(0, j-1)], ec_range[j], ec_range[min(4, j+1)]) for j in range(5)]
# 重心法解模糊
num_kp = num_ki = num_kd = 0.0
den = 0.0
for i in range(5):
for j in range(5):
w = mu_e[i] * mu_ec[j]
if w > 0:
num_kp += w * dkp_range[rule[i][j][0]]
num_ki += w * dki_range[rule[i][j][1]]
num_kd += w * dkd_range[rule[i][j][2]]
den += w
if den == 0:
return 0.0, 0.0, 0.0
return num_kp/den, num_ki/den, num_kd/den
# ---------- 非线性被控对象 ----------
def plant_with_nonlinearity(y, t, u):
"""二阶系统 + 死区 + 饱和"""
dydt = [y[1], 12*u - 2.5*y[1] - 4*y[0]]
return dydt
def nonlinear_u(u_raw):
"""死区+饱和非线性"""
if abs(u_raw) < 0.3:
return 0.0
u = np.sign(u_raw) * min(abs(u_raw), 3.0)
return u
# ---------- 模糊PID控制器 ----------
def fuzzy_pid_control(r, y, y_prev, e_prev, ec_prev, dt, Kp0, Ki0, Kd0):
"""计算控制量u"""
e = r - y
ec = (e - e_prev) / dt
# 输入归一化到[-1, 1]
e_norm = np.clip(e / 2.0, -1, 1)
ec_norm = np.clip(ec / 5.0, -1, 1)
# 模糊推理
dkp, dki, dkd = fuzzy_infer(e_norm, ec_norm)
# 更新PID参数
Kp = Kp0 + dkp
Ki = Ki0 + dki
Kd = Kd0 + dkd
# PID控制律(位置式)
u = Kp*e + Ki*e*dt + Kd*ec
return u, e, ec
# ---------- 仿真主程序 ----------
def simulate():
dt = 0.01
t_end = 10.0
t = np.arange(0, t_end, dt)
n = len(t)
# 状态变量
y = np.zeros(n)
y[0] = 0.0
u_hist = np.zeros(n)
# 控制器状态
e_prev = 0.0
ec_prev = 0.0
# PID初值
Kp0, Ki0, Kd0 = 2.5, 0.8, 0.4
# 参考输入(阶跃)
r = 1.0
for k in range(n-1):
# 计算控制量
u_raw, e_prev, ec_prev = fuzzy_pid_control(r, y[k], y[k], e_prev, ec_prev, dt, Kp0, Ki0, Kd0)
# 施加非线性
u = nonlinear_u(u_raw)
u_hist[k] = u
# 系统动态(使用odeint积分一步)
sol = odeint(plant_with_nonlinearity, [y[k], 0.0], [t[k], t[k+1]], args=(u,))
y[k+1] = sol[-1, 0]
return t, y, u_hist
# ---------- 运行与绘图 ----------
t, y, u_hist = simulate()
# 计算性能指标
y_final = y[-1]
overshoot = (np.max(y) - 1.0) / 1.0 * 100
# 稳态误差(取最后1秒平均)
ess = abs(1.0 - np.mean(y[-100:]))
print(f"超调量: {overshoot:.2f}%")
print(f"稳态误差: {ess:.4f}")
print(f"最终输出: {y_final:.4f}")
plt.figure(figsize=(10, 6))
plt.subplot(2, 1, 1)
plt.plot(t, y, 'b-', label='系统输出')
plt.axhline(y=1.0, color='r', linestyle='--', label='参考输入')
plt.ylabel('输出 y')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(t, u_hist, 'g-', label='控制量 u')
plt.ylabel('控制量')
plt.xlabel('时间 (s)')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('fuzzy_pid_nonlinear.png', dpi=100)
plt.show()
运行与验证
运行方式:保存为 fuzzy_pid_nonlinear.py,在装有numpy、scipy、matplotlib的Python环境中执行 python fuzzy_pid_nonlinear.py。
验证方法:
- 程序运行后输出超调量与稳态误差数值;
- 观察第一张图:输出曲线应在约3秒内进入参考值1.0的±5%误差带,且无明显持续振荡;
- 观察第二张图:控制量在初始阶段应达到饱和限幅(±3.0)以快速驱动系统,随后回落至死区阈值(0.3)以上维持稳态;
- 对比验证:将模糊推理部分注释掉(令ΔKp=ΔKi=ΔKd=0),使用固定PID运行同一仿真,固定PID会因死区产生明显稳态误差(通常大于0.05),而模糊PID的稳态误差应小于0.01。
预期输出(基于代码逻辑的确定性结果):模糊PID的稳态误差趋近于0(约0.005以内),超调量约5%~15%区间。由于死区非线性,固定PID必然存在稳态误差,而模糊PID通过增大Ki消除该误差。
关键说明
- 模糊规则表中档位数值对应输出论域中心点索引,重心法解模糊保证输出连续性;
- 输入归一化系数(e除以2.0,ec除以5.0)需根据实际误差范围调整,本案例中阶跃响应误差最大约1.0,误差变化率最大约5.0;
- 若需改善超调,可减小规则表中e=NB行对应的ΔKp档位(从3降为2),或增大ΔKd档位。