I am trying to use GEKKO MPC to control the level of a tank while manipulating the inlet flow. I want to model the GEKKO controller as a FOPDT. I got all the parameters I need, but I want to account for the time delay using the delay function. I am not sure of the exact position of this function since it is giving me an error when I put it in the code. When I remove it (i.e. no time delay) the code works fine but I want to be more realistic and put a time delay. Here is code attached:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from gekko import GEKKO
# Steady State Initial Condition
u2_ss=10.0
h_ss=50.0
x0 = np.empty(1)
x0[0]= h_ss
#%% GEKKO nonlinear MPC
m = GEKKO(remote=False)
m.time = [0,0.02,0.04,0.06,0.08,0.1,0.12,0.15,0.2]
Ac=30.0
# initial conditions
h0=50.0
q0=10.0
Kp=93.48425357240352
taup=1010.8757590561246
thetap= 3
m.q=m.MV(value=q0,lb=0,ub=100)
m.h= m.CV(value=h0)
m.delay(m.q,m.h,thetap)
m.Equation(taup * m.h.dt()==m.q*Kp -m.h)
#MV tuning
m.q.STATUS = 1
m.q.FSTATUS = 0
m.q.DMAX = 100
#CV tuning
m.h.STATUS = 1
m.h.FSTATUS = 1
m.h.TR_INIT = 2
m.h.TAU = 1.0
m.h.SP = 55.0
m.options.CV_TYPE = 2
m.options.IMODE = 6
m.options.SOLVER = 3
#%% define CSTR model
def cstr(x,t,u2,Ac):
q=u2
Ac=30.0
# States (2):
# the height of the tank (m)
h=x[0]
# Parameters:
# Calculate height derivative
dhdt=(q-5)/Ac
# Return xdot:
xdot = np.zeros(1)
xdot[0]= dhdt
return xdot
# Time Interval (min)
t = np.linspace(0,20,400)
# Store results for plotting
hsp=np.ones(len(t))*h_ss
h=np.ones(len(t))*h_ss
u2 = np.ones(len(t)) * u2_ss
# Set point steps
hsp[0:100] = 55.0
hsp[100:]=70.0
# Create plot
plt.figure(figsize=(10,7))
plt.ion()
plt.show()
# Simulate CSTR
for i in range(len(t)-1):
# simulate one time period (0.05 sec each loop)
ts = [t[i],t[i+1]]
y = odeint(cstr,x0,ts,args=(u2[i],Ac))
# retrieve measurements
h[i+1]= y[-1][0]
# insert measurement
m.h.MEAS=h[i+1]
m.h.SP=hsp[i+1]
# solve MPC
m.solve(disp=True)
# retrieve new q value
u2[i+1] = m.q.NEWVAL
# update initial conditions
x0[0]= h[i+1]
#%% Plot the results
plt.clf()
plt.subplot(2,1,1)
plt.plot(t[0:i],u2[0:i],'b--',linewidth=3)
plt.ylabel('inlet flow')
plt.subplot(2,1,2)
plt.plot(t[0:i],hsp[0:i],'g--',linewidth=3,label=r'$h_{sp}$')
plt.plot(t[0:i],h[0:i],'k.-',linewidth=3,label=r'$h_{meas}$')
plt.xlabel('time')
plt.ylabel('tank level')
plt.legend(loc='best')
plt.draw()
plt.pause(0.01)
The problem with your model is that the differential equation and the delay model are trying to solve the value of m.h based on the value of m.q. Both equations cannot be satisfied at the same time. The delay object requires that m.h is delayed version of m.q from 3 cycles ago. The differential equation requires the solution of the linear differential equation. They do not produce the same answer for m.h so this leads to an infeasible solution as the solver correctly reports.
m.delay(m.q,m.h,thetap)
m.Equation(taup * m.h.dt()==m.q*Kp -m.h)
Instead, you should create a new variable such as m.qd as a delayed version of m.q. The m.dq is then the input to the differential equation.
m.qd=m.Var()
m.delay(m.q,m.qd,thetap)
m.Equation(taup * m.h.dt()==m.qd*Kp -m.h)
Other Issues, Not Related to the Question
There were a couple other issues with your application.
The time synchronization between the simulator and controller are not correct. You should use the same cycle time for the simulator and controller. I changed the simulation time to t = np.linspace(0,20,201) for a 0.1 min cycle time.
The delay model requires that the controller have uniform time spacing because it is a discrete model. I changed the controller time spacing to m.time = np.linspace(0,2,21) or 0.1 min for a cycle time.
The simulator (solved with ODEINT) does not have input delay so there is model mismatch between the controller and simulator. This is still okay because it is a realistic scenario to have model mismatch but you'll need to realize that there will be some corrective control action based on feedback from the simulator. The controller is able to drive the level to the set point but there is chatter in the MV due to model mismatch and the squared error objective.
To improve the chatter, I switched to m.options.CV_TYPE=1, set a SPHI and SPLO dead-band, opened the initial trajectory with m.options.TR_OPEN=50, and added a move suppression with m.q.DCOST. These have the effect of achieving similar performance but without the valve chatter.
Here is the source code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from gekko import GEKKO
# Steady State Initial Condition
u2_ss=10.0
h_ss=50.0
x0 = np.empty(1)
x0[0]= h_ss
#%% GEKKO nonlinear MPC
m = GEKKO(remote=False)
m.time = np.linspace(0,2,21)
Ac=30.0
# initial conditions
h0=50.0
q0=10.0
Kp=93.48425357240352
taup=1010.8757590561246
thetap= 3
m.q=m.MV(value=q0,lb=0,ub=100)
m.qd=m.Var(value=q0)
m.h= m.CV(value=h0)
m.delay(m.q,m.qd,thetap)
m.Equation(taup * m.h.dt()==m.qd*Kp -m.h)
#MV tuning
m.q.STATUS = 1
m.q.FSTATUS = 0
m.q.DMAX = 100
m.q.DCOST = 1
#CV tuning
m.h.STATUS = 1
m.h.FSTATUS = 1
m.h.TR_INIT = 1
m.h.TR_OPEN = 50
m.h.TAU = 0.5
m.options.CV_TYPE = 1
m.options.IMODE = 6
m.options.SOLVER = 3
#%% define CSTR model
def cstr(x,t,u2,Ac):
q=u2
Ac=30.0
# States (2):
# the height of the tank (m)
h=x[0]
# Parameters:
# Calculate height derivative
dhdt=(q-5)/Ac
# Return xdot:
xdot = np.zeros(1)
xdot[0]= dhdt
return xdot
# Time Interval (min)
t = np.linspace(0,20,201)
# Store results for plotting
hsp=np.ones(len(t))*h_ss
h=np.ones(len(t))*h_ss
u2 = np.ones(len(t)) * u2_ss
# Set point steps
hsp[0:100] = 55.0
hsp[100:] = 70.0
# Create plot
plt.figure(figsize=(10,7))
plt.ion()
plt.show()
# Simulate CSTR
for i in range(len(t)-1):
# simulate one time period (0.05 sec each loop)
ts = [t[i],t[i+1]]
y = odeint(cstr,x0,ts,args=(u2[i],Ac))
# retrieve measurements
h[i+1]= y[-1][0]
# insert measurement
m.h.MEAS=h[i+1]
# for CV_TYPE = 1
m.h.SPHI=hsp[i+1]+0.05
m.h.SPLO=hsp[i+1]-0.05
# for CV_TYPE = 2
m.h.SP=hsp[i+1]
# solve MPC
m.solve(disp=False)
# retrieve new q value
u2[i+1] = m.q.NEWVAL
# update initial conditions
x0[0]= h[i+1]
#%% Plot the results
plt.clf()
plt.subplot(2,1,1)
plt.plot(t[0:i],u2[0:i],'b--',linewidth=3)
plt.ylabel('inlet flow')
plt.subplot(2,1,2)
plt.plot(t[0:i],hsp[0:i],'g--',linewidth=3,label=r'$h_{sp}$')
plt.plot(t[0:i],h[0:i],'k.-',linewidth=3,label=r'$h_{meas}$')
plt.xlabel('time')
plt.ylabel('tank level')
plt.legend(loc='best')
plt.draw()
plt.pause(0.01)
Related
I have a system with a non-constant delay. Does gekko support this type of problem and can it be handled in the MHE and MPC formulation?
Reading the docs I can see how to implement the delay, but I am not sure how the state estimation part of the MPC/MHE will handle this or if it is even capable to deal with such problems.
There is no problem to include variable time delay in estimation or control problems. There is a reformulation of the problem to allow for continuous 1st and 2nd derivatives that are needed for a gradient-based optimizer. I recommend that you use a cubic spline to create a continuous approximation of the discontinuous delay function. This way, the delay can be fractional such as theta=2.3. If the delay must be integer steps then set integer=True for the theta decision variable.
theta_ub = 30 # upper bound to dead-time
theta = m.FV(0,lb=0,ub=theta_ub); theta.STATUS=1
# add extrapolation points
td = np.concatenate((np.linspace(-theta_ub,min(t)-1e-5,5),t))
ud = np.concatenate((u[0]*np.ones(5),u))
# create cubic spline with t versus u
uc = m.Var(u); tc = m.Var(t); m.Equation(tc==time-theta)
m.cspline(tc,uc,td,ud,bound_x=False)
Here is an example of one cycle of Moving Horizon Estimation with a first-order plus dead-time (FOPDT) model with variable time delay. This example is from the Process Dynamics and Control online course.
import numpy as np
import pandas as pd
from gekko import GEKKO
import matplotlib.pyplot as plt
# Import CSV data file
# Column 1 = time (t)
# Column 2 = input (u)
# Column 3 = output (yp)
url = 'http://apmonitor.com/pdc/uploads/Main/data_fopdt.txt'
data = pd.read_csv(url)
t = data['time'].values - data['time'].values[0]
u = data['u'].values
y = data['y'].values
m = GEKKO(remote=False)
m.time = t; time = m.Var(0); m.Equation(time.dt()==1)
K = m.FV(2,lb=0,ub=10); K.STATUS=1
tau = m.FV(3,lb=1,ub=200); tau.STATUS=1
theta_ub = 30 # upper bound to dead-time
theta = m.FV(0,lb=0,ub=theta_ub); theta.STATUS=1
# add extrapolation points
td = np.concatenate((np.linspace(-theta_ub,min(t)-1e-5,5),t))
ud = np.concatenate((u[0]*np.ones(5),u))
# create cubic spline with t versus u
uc = m.Var(u); tc = m.Var(t); m.Equation(tc==time-theta)
m.cspline(tc,uc,td,ud,bound_x=False)
ym = m.Param(y); yp = m.Var(y)
m.Equation(tau*yp.dt()+(yp-y[0])==K*(uc-u[0]))
m.Minimize((yp-ym)**2)
m.options.IMODE=5
m.solve()
print('Kp: ', K.value[0])
print('taup: ', tau.value[0])
print('thetap: ', theta.value[0])
# plot results
plt.figure()
plt.subplot(2,1,1)
plt.plot(t,y,'k.-',lw=2,label='Process Data')
plt.plot(t,yp.value,'r--',lw=2,label='Optimized FOPDT')
plt.ylabel('Output')
plt.legend()
plt.subplot(2,1,2)
plt.plot(t,u,'b.-',lw=2,label='u')
plt.legend()
plt.ylabel('Input')
plt.xlabel('Time')
plt.show()
I am trying to use the python GEKKO non-linear regression tools to perform system identification of a second order over-damped system using the step response.
My code is as follows:
m = GEKKO()
m_input = m.Param(value=input)
m_time=m.Param(value=time)
m_T1 = m.FV(value=initT1, lb=T1bounds[0], ub=T1bounds[1])
m_T1.STATUS = 1
m_k = m.FV(value=initk,lb=100)
m_k.STATUS = 1
m_T2 = m.FV(value=initT2, lb=T2bounds[0], ub=T2bounds[1])
m_T2.STATUS = 1
m_output = m.CV(value=output)
m_output.FSTATUS=1
m.Equation(m_output==(m_k/(m_T1+m_T2))*(1+((m_T1/(m_T2-m_T1))*m.exp(-m_time/m_T2))-((m_T2/(m_T2-m_T1))*m.exp(-m_time/m_T1)))*m_input)
m.options.IMODE = 2
m.options.MAX_ITER = 10000
m.options.OTOL = 1e-8
m.options.RTOL = 1e-8
m.solve(disp=True)
The results have not been promising. It seems that the optimizer seems to get stuck in local minimas of the objective function leaving the objective function too high
The output of the solver is:
The final value of the objective function is 160453.282142838
---------------------------------------------------
Solver : IPOPT (v3.12)
Solution time : 7.60390000000189 sec
Objective : 160453.282605857
Successful solution
---------------------------------------------------
What can I do to improve the quality of the fit? Can I place limits on the objective function value?
Try using the equation for an underdamped 2nd order system instead of an overdamped 2nd order system. There is more information on the explicit solution to 2nd order systems on the Process Dynamics and Control website. An even better approach is to not use the explicit solution where it must be pre-determined if it is underdamped, critically damped, or overdamped. If the original 2nd order differential equation is used then the solver can decide if it is underdamped (overshoot with oscillations) or overdamped. Here is example code for fitting a 2nd order system.
import numpy as np
import pandas as pd
from gekko import GEKKO
import matplotlib.pyplot as plt
# Import CSV data file
# Column 1 = time (t)
# Column 2 = input (u)
# Column 3 = output (y)
url = 'http://apmonitor.com/pdc/uploads/Main/data_sopdt.txt'
data = pd.read_csv(url)
t = data['time'].values - data['time'].values[0]
u = data['u'].values
y = data['y'].values
m = GEKKO(remote=False)
m.time = t; time = m.Var(0); m.Equation(time.dt()==1)
K = m.FV(2,lb=0,ub=10); K.STATUS=1
tau = m.FV(3,lb=1,ub=200); tau.STATUS=1
theta_ub = 30 # upper bound to dead-time
theta = m.FV(0,lb=0,ub=theta_ub); theta.STATUS=1
zeta = m.FV(1,lb=0.1,ub=3); zeta.STATUS=1
# add extrapolation points
td = np.concatenate((np.linspace(-theta_ub,min(t)-1e-5,5),t))
ud = np.concatenate((u[0]*np.ones(5),u))
# create cubic spline with t versus u
uc = m.Var(u); tc = m.Var(t); m.Equation(tc==time-theta)
m.cspline(tc,uc,td,ud,bound_x=False)
ym = m.Param(y); yp = m.Var(y); xp = m.Var(y)
m.Equation(xp==yp.dt())
m.Equation((tau**2)*xp.dt()+2*zeta*tau*yp.dt()+yp==K*(uc-u[0]))
m.Minimize((yp-ym)**2)
m.options.IMODE=5
m.solve(disp=False)
print('Kp: ', K.value[0])
print('taup: ', tau.value[0])
print('thetap: ', theta.value[0])
print('zetap: ', zeta.value[0])
# plot results
plt.figure()
plt.subplot(2,1,1)
plt.plot(t,y,'k.-',lw=2,label='Process Data')
plt.plot(t,yp.value,'r--',lw=2,label='Optimized SOPDT')
plt.ylabel('Output')
plt.legend()
plt.subplot(2,1,2)
plt.plot(t,u,'b.-',lw=2,label='u')
plt.legend()
plt.ylabel('Input')
plt.show()
Please try this with your data to see if it gives a better solution.
I have a question about implementing uncertainty terms into the Gekko optimization problem. I am a beginner in coding and starting by adding parts from the "fish management" example. I have two main questions.
I would like to add an uncertainty term in the model (e.g. fluctuating future prices) but it seems like I am not understanding how the module works. I am trying to draw a random value from a certain distribution and put it into m.Var, 'ss', hoping the module will take each value at each time as t moves on. But it seems like the module does not work that way. I am wondering if there is any way I can implement uncertainty terms into the process.
Assuming the optimization problem to allocate initial land, A(0), between use A and E, is solved for a single agent by controlling land to convert, e, I am planning to expand this to a multiple agents problem. For example, if land quality, h, and land quantity A differ among agents, n, I am planning to solve multiple optimization problems using for algorithm by calling the initial m.Var value and some parameters from a loaded dataframe. If possible, may I have a brief comment on this plan?
# -*- coding: utf-8 -*-
from gekko import GEKKO
from scipy.stats import norm
from scipy.stats import truncnorm
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import operator
import math
import random
# create GEKKO model
m = GEKKO()
# Below, an agent is given initial land A(0) and makes a decision to convert this land to E
# Objective of an agent is to get maximum present utility(=log(income)) from both land use, income each period=(A+E(1-y))*Pa-C*u+E*Pe
# Uncertainty in future price lies for Pe, which I want to include with ss below
# After solving for a single agent, I want to solve this for all agents with different land quality h, risk aversion, mu, and land size A
# Then lastly collect data for total land use over time
# time points
n=51
year=10
k=50
m.time = np.linspace(0,year,n)
t=m.time
tt=t*(n-1)/year
tt = tt.astype(int)
ttt = np.exp(-t/(n-1))
# constants
Pa = 1
Pe = 1
C = 0.1
r = 0.05
y = 0.1
# distribution
# I am trying to generate a distribution, and use it as uncertainty term later in objective function
mu, sigma = 1, 0.1 # mean and standard deviation
#mu, sigman = df.loc[tt][2]
sn = np.random.normal(mu, sigma, n)
s= pd.DataFrame(sn)
ss=s.loc[tt][0]
# Control
# What is the difference between MV and CV? They give completely different solution
# MV seems to give correct answer
u = m.MV(value=0,lb=0,ub=10)
u.STATUS = 1
u.DCOST = 0
#u = m.CV(value=0,lb=0,ub=10)
# Variables
# m.Var and m.SV does not seem to lead to different results
# Can I call initial value from a dataset? for example, df.loc[tt][0] instead of 10 below?
# A = m.Var(value=df.loc[tt][0])
# h = m.Var(value=df.loc[tt][1])
A = m.SV(value=10)
E = m.SV(value=0)
#A = m.Var(value=10)
#E = m.Var(value=0)
t = m.Param(value=m.time)
Pe = m.Var(value=Pe)
d = m.Var(value=1)
# Equation
# It seems necessary to include restriction on u
m.Equation(A.dt()==-u)
m.Equation(E.dt()==u)
m.Equation(Pe.dt()==-1/k*Pe)
m.Equation(d==m.exp(-t*r))
m.Equation(A>=0)
# Objective (Utility)
J = m.Var(value=0)
# Final objective
# I want to include ss, uncertainty term in objective function
Jf = m.FV()
Jf.STATUS = 1
m.Connection(Jf,J,pos2='end')
#m.Equation(J.dt() == m.log(A*Pa-C*u+E*Pe))
m.Equation(J.dt() == m.log((A+E*(1-y))*Pa-C*u+E*Pe)*d)
#m.Equation(J.dt() == m.log(A*Pa-C*u+E*Pe*ss)*d)
# maximize profit
m.Maximize(Jf)
#m.Obj(-Jf)
# options
m.options.IMODE = 6 # optimal control
m.options.NODES = 3 # collocation nodes
m.options.SOLVER = 3 # solver (IPOPT)
# solve optimization problem
m.solve()
# print profit
print('Optimal Profit: ' + str(Jf.value[0]))
# collect data
# et=u.value
# print(et)
# At=A.value
# print(At)
# index = range(1, 2)
# columns = range(1, n+1)
# Ato=pd.DataFrame(index=index,columns=columns)
# Ato=At
# plot results
plt.figure(1)
plt.subplot(4,1,1)
plt.plot(m.time,J.value,'r--',label='profit')
plt.plot(m.time[-1],Jf.value[0],'ro',markersize=10,\
label='final profit = '+str(Jf.value[0]))
plt.plot(m.time,A.value,'b-',label='Agricultural Land')
plt.ylabel('Value')
plt.legend()
plt.subplot(4,1,2)
plt.plot(m.time,u.value,'k.-',label='adoption')
plt.ylabel('conversion')
plt.xlabel('Time (yr)')
plt.legend()
plt.subplot(4,1,3)
plt.plot(m.time,Pe.value,'k.-',label='Pe')
plt.ylabel('price')
plt.xlabel('Time (yr)')
plt.legend()
plt.subplot(4,1,4)
plt.plot(m.time,d.value,'k.-',label='d')
plt.ylabel('Discount factor')
plt.xlabel('Time (yr)')
plt.legend()
plt.show()
Try using the m.Param() (or m.FV, m.MV) instead of m.Var() to implement an uncertain value that is specified for each optimization. With m.Var(), an initial value can be specified but then it is calculated by the optimizer and the initial guess is no longer preserved.
Another way to implement uncertainty is to create an array of models with different parameters among the instances. Here is a simple example with K as a randomly selected value. This leads to 10 instances of the model that is optimized to a value of 40.
import numpy as np
from gekko import GEKKO
import matplotlib.pyplot as plt
# uncertain parameter
n = 10
K = np.random.rand(n)+1.0
m = GEKKO()
m.time = np.linspace(0,20,41)
# manipulated variable
p = m.MV(value=0, lb=0, ub=100)
p.STATUS = 1
p.DCOST = 0.1
p.DMAX = 20
# controlled variable
v = m.Array(m.CV,n)
for i in range(n):
v[i].STATUS = 1
v[i].SP = 40
v[i].TAU = 5
m.Equation(10*v[i].dt() == -v[i] + K[i]*p)
# solve optimal control problem
m.options.IMODE = 6
m.options.CV_TYPE = 2
m.solve()
# plot results
plt.figure()
plt.subplot(2,1,1)
plt.plot(m.time,p.value,'b-',LineWidth=2)
plt.ylabel('MV')
plt.subplot(2,1,2)
plt.plot([0,m.time[-1]],[40,40],'k-',LineWidth=3)
for i in range(n):
plt.plot(m.time,v[i].value,':',LineWidth=2)
plt.ylabel('CV')
plt.xlabel('Time')
plt.show()
In using GEKKO to model a dynamic system with an initial measurement, GEKKO seems to be ignoring the measurement completely even with FSTATUS turned on. What causes this and how can I get GEKKO to recognize the initial measurement?
I would expect the solver to take the initial measurement into account an adjust the solution accordingly.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
# measurement
tm = 0
xm = 25
m = GEKKO()
m.time = np.linspace(0,20,41)
tau = 10
b = m.Param(value=50)
K = m.Param(value=0.8)
# Manipulated Variable
u = m.MV(value=0, lb=0, ub=100)
u.STATUS = 1 # allow optimizer to change
u.DCOST = 0.1
u.DMAX = 30
# Controlled Variable
x = m.CV(value=0,name='x')
x.STATUS = 1 # add the SP to the objective
m.options.CV_TYPE = 2 # squared error
x.SP = 40 # set point
x.TR_INIT = 1 # set point trajectory
x.TAU = 5 # time constant of trajectory
x.FSTATUS = 1
x.MEAS = xm
# Process model
m.Equation(tau*x.dt() == -x + K*u)
m.options.IMODE = 6 # control
m.solve()
# get additional solution information
import json
with open(m.path+'//results.json') as f:
results = json.load(f)
plt.figure()
plt.subplot(2,1,1)
plt.plot(m.time,u.value,'b-',label='MV Optimized')
plt.legend()
plt.ylabel('Input')
plt.subplot(2,1,2)
plt.plot(tm,xm,'ro', label='Measurement')
plt.plot(m.time,results['x.tr'],'k-',label='Reference Trajectory')
plt.plot(m.time,results['x.bcv'],'r--',label='CV Response')
plt.ylabel('Output')
plt.xlabel('Time')
plt.legend()
plt.show()
Gekko ignores the measurement on the first cycle for initialization of the MPC. If you do another solve then it uses the measurement.
m.solve() # for MPC initialization
x.MEAS = xm
m.solve() # update initial condition with measurement
The feedback status (FSTATUS) is a first-order filter for the measurements that ranges between 0 (no update) and 1 (full measurement update).
MEAS = LSTVAL * (1-FSTATUS) + MEAS * FSTATUS
The new measurement (MEAS) is then used in the bias calculation. There is an unbiased (raw prediction not affected by measurements) model prediction and a biased model prediction. The bias is calculated as the difference between the unbiased model prediction and the measurement.
BIAS = MEAS - UNBIASED_MODEL
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
# measurement
tm = 0
xm = 25
m = GEKKO()
m.time = np.linspace(0,20,41)
tau = 10
b = m.Param(value=50)
K = m.Param(value=0.8)
# Manipulated Variable
u = m.MV(value=0, lb=0, ub=100)
u.STATUS = 1 # allow optimizer to change
u.DCOST = 0.1
u.DMAX = 30
# Controlled Variable
x = m.CV(value=0,name='x')
x.STATUS = 1 # add the SP to the objective
m.options.CV_TYPE = 2 # squared error
x.SP = 40 # set point
x.TR_INIT = 1 # set point trajectory
x.TAU = 5 # time constant of trajectory
x.FSTATUS = 1
# Process model
m.Equation(tau*x.dt() == -x + K*u)
m.options.IMODE = 6 # control
m.solve(disp=False)
m.options.TIME_SHIFT = 0
x.MEAS = xm
m.solve(disp=False)
# turn off time shift, only for initialization
m.options.TIME_SHIFT = 1
# get additional solution information
import json
with open(m.path+'//results.json') as f:
results = json.load(f)
plt.figure()
plt.subplot(2,1,1)
plt.plot(m.time,u.value,'b-',label='MV Optimized')
plt.legend()
plt.ylabel('Input')
plt.ylim([-5,105])
plt.subplot(2,1,2)
plt.plot(tm,xm,'ro', label='Measurement')
plt.plot(m.time,results['x.tr'],'k-',label='Reference Trajectory')
plt.plot(m.time,results['x.bcv'],'r--',label='CV Response Biased')
plt.plot(m.time,x.value,'g:',label='CV Response Unbiased')
plt.ylim([-1,41])
plt.ylabel('Output')
plt.xlabel('Time')
plt.legend()
plt.show()
This is how it currently works because there are no LSTVAL or unbiased model predictions for the calculations mentioned above. The first cycle calculates those values and allows updating on subsequent cycles. If you do need the updated values on the first cycle then you can solve with option m.option.TIME_SHIFT=0 on the second solve to not update the initial conditions of your model. You will want to change TIME_SHIFT=1 for subsequent cycles to have the expected time-progression of the dynamic model.
This Model Predictive Control (MPC) example using GEKKO (relating gas pedal movement to car velocity), doesn't explicitly state a cost function to minimize:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO()
m.time = np.linspace(0,20,41)
# Parameters
mass = 500
b = m.Param(value=50)
K = m.Param(value=0.8)
# Manipulated variable
p = m.MV(value=0, lb=0, ub=100)
p.STATUS = 1 # allow optimizer to change
p.DCOST = 0.1 # smooth out gas pedal movement
p.DMAX = 20 # slow down change of gas pedal
# Controlled Variable
v = m.CV(value=0)
v.STATUS = 1 # add the SP to the objective
m.options.CV_TYPE = 2 # squared error
v.SP = 40 # set point
v.TR_INIT = 1 # set point trajectory
v.TAU = 5 # time constant of trajectory
# Process model
m.Equation(mass*v.dt() == -v*b + K*b*p)
m.options.IMODE = 6 # control
m.solve(disp=False)
# get additional solution information
import json
with open(m.path+'//results.json') as f:
results = json.load(f)
plt.figure()
plt.subplot(2,1,1)
plt.plot(m.time,p.value,'b-',label='MV Optimized')
plt.legend()
plt.ylabel('Input')
plt.subplot(2,1,2)
plt.plot(m.time,results['v1.tr'],'k-',label='Reference Trajectory')
plt.plot(m.time,v.value,'r--',label='CV Response')
plt.ylabel('Output')
plt.xlabel('Time')
plt.legend(loc='best')
plt.show()
Can anyone give me an algebraic expression for what cost function GEKKO is minimizing by default for this problem?
With 'CV_TYPE = 2', your cost function is going to be the sum of squared error between setpoint and predicted CV value throughout the horizon length that you defined (m.time).
Please see the below link for detailed equations for squared error form and L1 (CV_TYPE = 1) form of the MPC objective function.
http://apmonitor.com/do/index.php/Main/ControllerObjective
Junho