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。

验证方法:

  1. 程序运行后输出超调量与稳态误差数值;
  2. 观察第一张图:输出曲线应在约3秒内进入参考值1.0的±5%误差带,且无明显持续振荡;
  3. 观察第二张图:控制量在初始阶段应达到饱和限幅(±3.0)以快速驱动系统,随后回落至死区阈值(0.3)以上维持稳态;
  4. 对比验证:将模糊推理部分注释掉(令Δ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档位。