3

I'm trying to build a MPC model with previously estimated values of k, tau and theta in a FOPDT equation. I implemented this sugestion to estimate the dead-time using cspline: How to estimate theta value in FOPDT equation using gekko?.

However, I can't do the same thing with MPC because I can't use a MV as the second argument of cspline in order to apply the dead-time. If I use a gekko variable for this second argument instead, the MV's don't change because they are not present in the equation. I have two MV's, one CV and two disturbance variables. How can I apply the dead-time in this case? Thank you

import numpy as np
import time
import plotly.express as px 
from gekko import GEKKO
import json
from datetime import datetime

start_time_count = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print("start_time: ", start_time_count)

dist1 = d19jcSliceMPC['Cond_PID_SP'].values
mv1 = d19jcSliceMPC['Front_PID_PV'].values
mv2 = d19jcSliceMPC['Rear_PID_PV'].values
dist2 = d19jcSliceMPC['Pull_rolling'].values
cv1 = d19jcSliceMPC['Cond_Center_Btm_TC'].values

run_time = 3.0 * 60.0 
n = int(0.2*run_time)

Tsp1 = 1163.0 #setpoint

m = GEKKO(name='MPCbtmTC',remote=False)
m.time = np.linspace(0,n-1,n)
time_uc = m.Param(m.time)

# MV
Front = m.MV(value=mv1)
Front_ss = m.Param(value=mv1[0])
KpFront = m.Param(value=1.685312)
tauFront = m.Param(value=5.770839)
thetaFront = m.Param(value=0.114705)
t1 = np.linspace(-1,n-1,n)
ucFront = m.Var(); tm1 = m.Var(); m.Equation(tm1==time_uc-thetaFront)
m.cspline(tm1,ucFront,t1,np.array(Front),bound_x=False)

Rear = m.MV(value=mv2)
Rear_ss = m.Param(value=mv2[0])
KpRear = m.Param(value=0.1)
tauRear = m.Param(value=36.0)
thetaRear = m.Param(value=3.779397)
t2 = np.linspace(-4,n-1,n)
ucRear = m.Var(); tm2 = m.Var(); m.Equation(tm2==time_uc-thetaRear)
m.cspline(tm2,ucRear,t2,np.array(Rear),bound_x=False)

Front.STATUS = 1  # use to control temperature
Front.FSTATUS = 0 # no feedback measurement
Front.LOWER = 1160.0
Front.UPPER = 1200.0
Front.DMAX = 2.0
Front.COST = 0.0
Front.DCOST = 1.0e-4

Rear.STATUS = 1  # use to control temperature
Rear.FSTATUS = 0 # no feedback measurement
Rear.LOWER = 1180.0
Rear.UPPER = 1220.0
Rear.DMAX = 2.0
Rear.COST = 0.0
Rear.DCOST = 1.0e-4

# Parameters (disturbance)
CondSP = m.Param(value=dist1)
CondSP_ss = m.Param(value=dist1[0])
KpCondSP = m.Param(value=4.990293)
tauCondSP = m.Param(value=29.272660)
thetaCondSP = m.Param(value=2.554230)
t3 = np.linspace(-3,n-1,n)
ucCondSP = m.Var(); tm3 = m.Var(); m.Equation(tm3==time_uc-thetaCondSP)
m.cspline(tm3,ucCondSP,t3,dist1,bound_x=False)

Pull = m.Param(value=dist2)
Pull_ss = m.Param(value=dist2[0])
KpPull = m.Param(value=0.151304)
tauPull = m.Param(value=4.128567)
thetaPull = m.Param(value=0.0)
t4 = np.linspace(-0,n-1,n)
ucPull = m.Var(); tm4 = m.Var(); m.Equation(tm4==time_uc-thetaPull)
m.cspline(tm4,ucPull,t4,dist2,bound_x=False)

# Controlled variable
TC1_ss = m.Param(value=cv1[0])
TC1 = m.CV(value=TC1_ss.value)
TC1.STATUS = 1     # minimize error with setpoint range
TC1.FSTATUS = 1    # receive measurement
TC1.TR_INIT = 2    # reference trajectory
TC1.TAU = 2       # time constant for response

# Equation
m.Equation(TC1.dt()==(KpFront*(ucFront-Front_ss)-(TC1-TC1_ss))/tauFront + (KpRear*(ucRear-Rear_ss)-(TC1-TC1_ss))/tauRear+ 
                     (KpCondSP*(ucCondSP-CondSP_ss)-(TC1-TC1_ss))/tauCondSP + (KpPull*(ucPull-Pull_ss)-(TC1-TC1_ss))/tauPull)

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

TC1.MEAS = cv1[0]
# input setpoint with deadband +/- DT
DT = 0.1
TC1.SPHI = Tsp1 + DT
TC1.SPLO = Tsp1 - DT
# solve MPC
m.solve(disp=False)    
# get additional solution information
with open(m.path+'//results.json') as f:
    results = json.load(f)

finish_time_count = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print("end_time: ", finish_time_count)

df_plot = pd.DataFrame({'DateTime' : d19jcSliceMPC.time,
                        'CV (TC1)' : results['v9.bcv'],
                        'SPHI' : results['v9.sp_hi'],
                        'SPLO' : results['v9.sp_lo'],
                        'Front' : Front,
                        'Rear' : Rear})
figGekko = px.line(df_plot, 
                   x='DateTime',
                   y=['CV (TC1)','SPHI','SPLO','Front','Rear'],
                   labels={"value": "Degrees Celsius"},
                   title = "MPC")
figGekko.update_layout(legend_title_text='')
figGekko.show()

Edit:

As suggested, i changed to

ucFront = m.Var(); m.Equation(ucFront==Front) 
tm1 = m.Var(); m.Equation(tm1==time_uc-thetaFront)
m.cspline(tm1,ucFront,t1,np.array(Front),bound_x=False)

but I get this error:

Error: Exception: Access Violation
At line 359 of file ./f90/cqp.f90
Traceback: not available, compile with -ftrace=frame or -ftrace=full

Error: 'results.json' not found. Check above for additional error details

If I leave just the MV as the unshifted input I get the same error as before

TypeError: y_data must be a python list or numpy array
  • 1
    Daniel, I think you might want to introduce another set of GEKKO MVs for the unshifted inputs. For example, in the m.cspline(shifted time, shifted input, unshifted time, unshifted input), set the shifted arguments as Gekko variables (m.Var) and set the unshifted inputs as Gekko MVs that you left them as non-Gekko variables in your code. – Junho Park Dec 15 '20 at 19:24

1 Answers1

1

As Junho Park correctly observed, you can create another variable x such as:

MV = m.MV()  # use MV in MPC application

x  = m.Var() # use x with cspline function
m.Equation(x==MV) # connect x and MV

The cspline object complains about the MV because it wants a calculated variable instead of a type that is by default fixed and consumes a degree of freedom.

John Hedengren
  • 12,068
  • 1
  • 21
  • 25
  • Thank you for your answer. It still didn't solve the problem, please check the edit to my question. – Daniel Baptista Dec 17 '20 at 11:06
  • It looks like there was a problem with the APOPT solver. Could you try the IPOPT solver instead with `m.options.SOLVER=3`. I can't reproduce the error because I don't have the data files. – John Hedengren Dec 17 '20 at 16:18
  • I got `Exception: @error: Solution Not Found` . Also, in both cases, the MV's keep the initial values and the Var's used with cspline are set to 0 – Daniel Baptista Dec 17 '20 at 16:29
  • And with `m.solve(disp=True)` I got this reason for the error `EXIT: Converged to a point of local infeasibility. Problem may be infeasible. An error occured. The error code is 2` – Daniel Baptista Dec 17 '20 at 17:05
  • 1
    The solver found that the equations can not be satisfied with the given equations and constraints. Here are some things to try: (1) Set `m.options.NODES=2` to test removal of internal nodes, (2) remove constraints that may be limiting the solver from converging, especially constraints on Variables and CVs, (3) add constraints on the MVs to help it converge by giving it a smaller search area. – John Hedengren Dec 18 '20 at 18:44
  • 1
    With (1) I got a successful solution but the MV's are not changed like it happened before wihtout the `m.Equation(ucFront==Front)` equation. In (2) and (3) I tried increasing the setpoint deadband and change the MV's bounds and DMAX but with no result. This problem only happens when I add the theta with cspline to the MV's, if I remove those lines and place the MV directly in the equation, the solver works fine but without the dead-time from the MV's. – Daniel Baptista Dec 20 '20 at 14:51
  • 1
    Maybe try setting `theta=0` to verify the solution with `m.Equation(ucFront==Front)`. Could the solution with `m.options.SOLVER=1` be correct? I thought the prior attempts with `m.options.SOLVER=3` gave a failed solution. You can't trust a failed solution. – John Hedengren Dec 20 '20 at 16:46
  • 1
    Yes, `m.options.SOLVER=3` gave a failed solution, except in (1) with `m.options.NODES=2`. Before that, on my original post, the soutions with `m.options.SOLVER=1` only succeeded when I didn't use cspline on the MV's, that's what I was saying – Daniel Baptista Dec 20 '20 at 17:50
  • 1
    With `theta=0` I get a similar result to (1) with `m.options.NODES=2`. I get a solution but the MV's don't change. – Daniel Baptista Dec 20 '20 at 17:54
  • 1
    Just to clarify, if I remove the cspline on the MV's and use the `m.options.SOLVER=3` I still get a successful solution just like with `m.options.SOLVER=1`, so it does not seem to be a solver problem. But in those cases I don't have the dead-time applied to the MV's – Daniel Baptista Dec 20 '20 at 18:10
  • 1
    One other thing to try is to set `bound_x=True` so that the optimizer doesn't search outside the bounds of the cspline. – John Hedengren Dec 20 '20 at 19:42
  • 1
    It still does not work with that option... Any more guesses of what it can be? – Daniel Baptista Jan 04 '21 at 11:37
  • 1
    What error does it produce? Is it Access Violation, max iterations, successful solution but with bad results, or infeasible solution? – John Hedengren Jan 04 '21 at 17:08
  • I get the same results with bound_x=True. If I set the deadtime (theta) to the normal value I get infeasable solution. If I set it to 0 I get a solution that is incorrect, because the MV's don't change. – Daniel Baptista Jan 05 '21 at 10:46