如何避免大延时系统的作动器曲线振荡?

问题描述

我将 Gekko 的计算结果放入队列,80s 后,将队列的第一个值写入 TCLab Arduino。我用这种方法模拟了一个工厂大时延系统,然后我优化了 Gekko 参数以达到更好的控制效果。 当我在模型中添加延迟时,我得到了一条振荡曲线:

oscillation curve

我调整了 dcosT 和 DMAX,然后我得到了更好的曲线。但这不是理想曲线。

optimization curve

我认为理想的曲线就是这样的曲线。

ideal curve

这是代码

import tclab
import numpy as np
import time
import matplotlib.pyplot as plt
from gekko import GEKKO
import _thread
import queue
import threading
import json

# Connect to Arduino
a = tclab.TCLab()

R = threading.Lock()

# Get Version
print(a.version)

# Turn LED on
print('LED On')
a.LED(100)

# Simulate a time delay
delay = 40

# Run time in minutes
run_time = 60.0

# Number of cycles
loops = int(60.0*run_time)
tm = np.zeros(loops)

# Temperature (K)
T1 = np.ones(loops) * a.T1 # temperature (degC)
Tsp1 = np.ones(loops) * 40.0 # set point (degC)

# heater values
Q1s = np.ones(loops) * 0.0

#########################################################
# Initialize Model
#########################################################
# use remote=True for MacOS
m = GEKKO(name='tclab-mpc',remote=False)

# 30 second time horizon
m.time = np.linspace(0,240,121)

# Parameters
Q1_ss = m.Param(value=20)
TC1_ss = m.Param(value=a.T1)
Kp = m.Param(value=1.5)
tau = m.Param(value=160.0)

# Manipulated variable
Q1 = m.MV(value=Q1_ss.VALUE,name='q1')
Q1.STATUS = 1  # use to control temperature
Q1.FSTATUS = 0 # no Feedback measurement
Q1.LOWER = 0.0
Q1.UPPER = 100.0
Q1.DMAX = 2
Q1.dcosT = 10.0

# Controlled variable
TC1 = m.CV(value=TC1_ss.VALUE,name='tc1')
TC1.STATUS = 1     # minimize error with setpoint range
TC1.FSTATUS = 1    # receive measurement
TC1.TR_INIT = 2    # reference trajectory
# TC1.TAU = 100       # time constant for response

# 添加延时
Q1d=m.Var()
m.delay(Q1,Q1d,40)
# Equation
m.Equation(tau * TC1.dt() + TC1 == Kp * Q1d)

# Global Options
m.options.IMODE   = 6 # MPC
m.options.CV_TYPE = 1 # Objective type
m.options.NODES   = 2 # collocation nodes
m.options.soLVER  = 1 # 1=APOPT,3=IPOPT
##################################################################

# Create plot
plt.figure()
plt.ion()
plt.show()

filter_tc1 = []

q = queue.Queue()
flag = False
def setQ1():
    global flag
    global a
    last_time = time.time()
    while True:
        sleep_max = 2.0
        sleep = sleep_max - (time.time() - last_time)
        print("q.qsize()",q.qsize())
        if sleep >= 0.01:
            time.sleep(sleep)
        else:
            time.sleep(0.01)

        # Record time and change in time
        last_time = time.time()
        if q.qsize() >= delay:
            q1 = q.get()
            print(f'Q1: {q1} ')
            try:
                R.acquire()
                print("write Q1:",a.Q1(float(q1)))
                R.release()
            except:
                print("转换报错!")
                pass

_thread.start_new_thread(setQ1,())

# Rolling average filtering
def movefilter(predata,new,n):
    if len(predata) < n:
        predata.append(new)
    else:
        predata.pop(0)
        predata.append(new)
    return np.average(predata)

# Main Loop
start_time = time.time()
prev_time = start_time
last_Q1 = 0
try:
    for i in range(1,loops):
        # Sleep time
        sleep_max = 2.0
        sleep = sleep_max - (time.time() - prev_time)
        if sleep>=0.01:
            time.sleep(sleep)
        else:
            time.sleep(0.01)

        # Record time and change in time
        t = time.time()
        dt = t - prev_time
        prev_time = t
        tm[i] = t - start_time

        # Read temperatures in Kelvin
        R.acquire()
        curr_T1 = a.T1
        R.release()
        last_T1 = curr_T1
        avg_T1 = movefilter(filter_tc1,last_T1,3)
        T1[i] = curr_T1
        # T1[i] = a.T1
        # T2[i] = a.T2

        ###############################
        ### MPC CONTROLLER          ###
        ###############################
        TC1.MEAS = avg_T1
        # input setpoint with deadband +/- DT
        DT = 0.5
        TC1.SPHI = Tsp1[i] + DT
        TC1.SPLO = Tsp1[i] - DT
        try:
            # solve MPC
            m.solve(disp=False)
        except:
            pass
        # test for successful solution
        if (m.options.APPSTATUS==1):
            # retrieve the first Q value
            Q1s[i] = Q1.NEWVAL
            with open(m.path+'//results.json') as f:
                results = json.load(f)
        else:
            # not successful,set heater to zero
            Q1s[i] = 0

        # Write output (0-100)
        q.put(Q1s[i])

        # Plot
        plt.clf()
        ax=plt.subplot(2,1,1)
        ax.grid()
        plt.plot(tm[0:i],T1[0:i],'ro',MarkerSize=3,label=r'$T_1$')
        plt.plot(tm[0:i],Tsp1[0:i],'b-',label=r'$T_1 Setpoint$')
        plt.plot(tm[i]+m.time,results['tc1.bcv'],'k-.',\
                 label=r'$T_1$ predicted',linewidth=3)
        plt.plot(tm[i]+m.time,results['tc1.tr_hi'],'k--',\
                 label=r'$T_1$ trajectory')
        plt.plot(tm[i]+m.time,results['tc1.tr_lo'],'k--')
        plt.ylabel('Temperature (degC)')
        plt.legend(loc='best')
        ax=plt.subplot(2,2)
        ax.grid()
        plt.plot(tm[0:i],Q1s[0:i],'r-',linewidth=3,label=r'$Q_1$')
        plt.plot(tm[i]+m.time,Q1.value,\
                 label=r'$Q_1$ plan',linewidth=3)
        # plt.plot(tm[0:i],Q2s[0:i],'b:',label=r'$Q_2$')
        plt.ylabel('Heaters')
        plt.xlabel('Time (sec)')
        plt.legend(loc='best')
        plt.draw()
        plt.pause(0.05)

    # Turn off heaters
    a.Q1(0)
    a.Q2(0)
    print('Shutting down')
    a.close()

# Allow user to end loop with Ctrl-C
except KeyboardInterrupt:
    # disconnect from Arduino
    a.Q1(0)
    a.Q2(0)
    print('Shutting down')
    a.close()

# Make sure serial connection still closes when there's an error
except:
    # disconnect from Arduino
    a.Q1(0)
    a.Q2(0)
    print('Error: Shutting down')
    a.close()
    raise

解决方法

如果 MPC 应用程序给出意外结果,通常可以通过显示无偏和有偏模型预测以及参考轨迹来诊断。使用模型时,您应该考虑以下几点。

  1. 将方程更改为偏差变量形式。温度的稳态点是 TC1=TC1_ssQ1d=0。更新后的方程为:
m.Equation(tau * TC1.dt() + (TC1-TC1_ss) == Kp * (Q1d-0))
  1. 使用稳态模拟以 IMODE=1 初始化所有变量。
# Steady-state initialization
m.options.IMODE = 1
m.solve()

# Global Options
m.options.IMODE   = 6 # MPC
m.options.CV_TYPE = 1 # Objective type
m.options.NODES   = 2 # Collocation nodes
m.options.SOLVER  = 1 # 1=APOPT,3=IPOPT

这有助于解决问题,但不能消除它。任何时候出现设备/模型不匹配时,如果模型增益太高,MPC 就会太迟钝,或者如果模型增益太低,就会产生振荡。振荡是因为 MPC 更新了测量值,必须根据更新的信息重新计算移动计划。

First

Last

import tclab
import numpy as np
import time
import matplotlib.pyplot as plt
from gekko import GEKKO
import _thread
import queue
import threading
import json

# Connect to Arduino
a = tclab.TCLabModel()

R = threading.Lock()

# Turn LED on
print('LED On')
a.LED(100)

# Simulate a time delay
delay = 40

# Run time in minutes
run_time = 60.0

# Number of cycles
loops = int(60.0*run_time)
tm = np.zeros(loops)

# Temperature (K)
T1 = np.ones(loops) * a.T1 # temperature (degC)
Tsp1 = np.ones(loops) * 40.0 # set point (degC)

# heater values
Q1s = np.ones(loops) * 0.0

#########################################################
# Initialize Model
#########################################################
# use remote=True for MacOS
m = GEKKO(name='tclab-mpc',remote=False)

# 30 second time horizon
m.time = np.linspace(0,240,121)

# Parameters
Q1_ss = m.Param(value=20)
TC1_ss = m.Param(value=a.T1)
Kp = m.Param(value=1.5)
tau = m.Param(value=160.0)

# Manipulated variable
Q1 = m.MV(value=Q1_ss.VALUE,name='q1')
Q1.STATUS = 1  # use to control temperature
Q1.FSTATUS = 0 # no feedback measurement
Q1.LOWER = 0.0
Q1.UPPER = 100.0
Q1.DMAX = 2
Q1.DCOST = 10.0

# Controlled variable
TC1 = m.CV(value=TC1_ss.VALUE,name='tc1')
TC1.STATUS = 1     # minimize error with setpoint range
TC1.FSTATUS = 1    # receive measurement
TC1.TR_INIT = 2    # reference trajectory
# TC1.TAU = 100       # time constant for response

# 添加延时
Q1d=m.Var()
m.delay(Q1,Q1d,40)
# Equation
m.Equation(tau * TC1.dt() + (TC1-TC1_ss) == Kp * Q1d)

# Steady-state initialization
m.options.IMODE = 1
m.solve()

# Global Options
m.options.IMODE   = 6 # MPC
m.options.CV_TYPE = 1 # Objective type
m.options.NODES   = 2 # Collocation nodes
m.options.SOLVER  = 1 # 1=APOPT,3=IPOPT
##################################################################

# Create plot
plt.figure()
plt.ion()
plt.show()

filter_tc1 = []

q = queue.Queue()
flag = False
def setQ1():
    global flag
    global a
    last_time = time.time()
    while True:
        sleep_max = 2.0
        sleep = sleep_max - (time.time() - last_time)
        print("q.qsize()",q.qsize())
        if sleep >= 0.01:
            time.sleep(sleep)
        else:
            time.sleep(0.01)

        # Record time and change in time
        last_time = time.time()
        if q.qsize() >= delay:
            q1 = q.get()
            print(f'Q1: {q1} ')
            try:
                R.acquire()
                print("write Q1:",a.Q1(float(q1)))
                R.release()
            except:
                print("转换报错!")
                pass

_thread.start_new_thread(setQ1,())

# Rolling average filtering
def movefilter(predata,new,n):
    if len(predata) < n:
        predata.append(new)
    else:
        predata.pop(0)
        predata.append(new)
    return np.average(predata)

# Main Loop
start_time = time.time()
prev_time = start_time
last_Q1 = 0
try:
    for i in range(1,loops):
        # Sleep time
        sleep_max = 2.0
        sleep = sleep_max - (time.time() - prev_time)
        if sleep>=0.01:
            time.sleep(sleep)
        else:
            time.sleep(0.01)

        # Record time and change in time
        t = time.time()
        dt = t - prev_time
        prev_time = t
        tm[i] = t - start_time

        # Read temperatures in Kelvin
        R.acquire()
        curr_T1 = a.T1
        R.release()
        last_T1 = curr_T1
        avg_T1 = movefilter(filter_tc1,last_T1,3)
        T1[i] = curr_T1
        # T1[i] = a.T1
        # T2[i] = a.T2

        ###############################
        ### MPC CONTROLLER          ###
        ###############################
        TC1.MEAS = avg_T1
        # input setpoint with deadband +/- DT
        DT = 0.5
        TC1.SPHI = Tsp1[i] + DT
        TC1.SPLO = Tsp1[i] - DT
        try:
            # solve MPC
            m.solve(disp=False)
        except:
            pass
        # test for successful solution
        if (m.options.APPSTATUS==1):
            # retrieve the first Q value
            Q1s[i] = Q1.NEWVAL
            with open(m.path+'//results.json') as f:
                results = json.load(f)
        else:
            # not successful,set heater to zero
            Q1s[i] = 0

        # Write output (0-100)
        q.put(Q1s[i])

        # Plot
        plt.clf()
        ax=plt.subplot(2,1,1)
        ax.grid()
        plt.plot(tm[0:i],T1[0:i],'ro',markersize=3,label=r'$T_1$')
        plt.plot(tm[0:i],Tsp1[0:i],'b-',label=r'$T_1 Setpoint$')
        plt.plot(tm[i]+m.time,results['tc1.bcv'],'k-.',\
                 label=r'$T_1$ predicted',linewidth=3)
        plt.plot(tm[i]+m.time,results['tc1.tr_hi'],'k--',\
                 label=r'$T_1$ trajectory')
        plt.plot(tm[i]+m.time,results['tc1.tr_lo'],'k--')
        plt.ylabel('Temperature (degC)')
        plt.legend(loc='best')
        ax=plt.subplot(2,2)
        ax.grid()
        plt.plot(tm[0:i],Q1s[0:i],'r-',linewidth=3,label=r'$Q_1$')
        plt.plot(tm[i]+m.time,Q1.value,\
                 label=r'$Q_1$ plan',linewidth=3)
        # plt.plot(tm[0:i],Q2s[0:i],'b:',LineWidth=3,label=r'$Q_2$')
        plt.ylabel('Heaters')
        plt.xlabel('Time (sec)')
        plt.legend(loc='best')
        plt.draw()
        plt.pause(0.05)

    # Turn off heaters
    a.Q1(0)
    a.Q2(0)
    print('Shutting down')
    a.close()

# Allow user to end loop with Ctrl-C
except KeyboardInterrupt:
    # Disconnect from Arduino
    a.Q1(0)
    a.Q2(0)
    print('Shutting down')
    a.close()

# Make sure serial connection still closes when there's an error
except:
    # Disconnect from Arduino
    a.Q1(0)
    a.Q2(0)
    print('Error: Shutting down')
    a.close()
    raise

this paper 的图 14 量化了模型不匹配的 MPC 性能。

MPC performance

  • Hedengren,JD,Eaton,AN,工业动态系统估计方法概述,优化和工程,Springer,第 18 (1) 卷,2017 年,第 155-178 页,DOI:10.1007/s11081-015-9295- 9.