Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
212 views
in Technique[技术] by (71.8m points)

python 3.x - How to avoid the actuator curve oscillation of large time delay system?

I put the result of Gekko's calculation into the queue, after 80s, write the first value of the queue to TCLab Arduino. I use this method to simulate a factory large time delay system, then I optimize Gekko parameters to achieve better control effect. When I add a delay in the model, I got a oscillative curve: oscillation curve

I adjust the DCOST and DMAX, then I got a better curve. But this is not an ideal curve. optimization curve

I think the ideal curve is like this curve. ideal curve

Here is the code.

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-',MarkerSize=3,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,1,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,'k-.',
                 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If an MPC application is giving unexpected results, it can typically be diagnosed by showing the unbiased and biased model predictions and the reference trajectory. Here are a couple things that you should consider with you model.

  1. Change the equation to be in deviation variable form. The steady state point for temperature is TC1=TC1_ss and Q1d=0. The updated equation is:
m.Equation(tau * TC1.dt() + (TC1-TC1_ss) == Kp * (Q1d-0))
  1. Use a steady-state simulation to initialize all of the variables with 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

This helps the problem but does not eliminate it. Anytime there is plant / model mismatch, the MPC will be too sluggish if the model gain is too high or create oscillations if the model gain is too low. The oscillations are because the MPC has updated measurements and has to recalculate the move plan based on updated information.

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-',markersize=3,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,1,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,'k-.',
                 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

Figure 14 of this paper quantifies the MPC performance with model mismatch.

MPC performance

  • Hedengren, J. D., Eaton, A. N., Overview of Estimation Methods for Industrial Dynamic Systems, Optimization and Engineering, Springer, Vol 18 (1), 2017, pp. 155-178, DOI: 10.1007/s11081-015-9295-9.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...