I'm trying to model a biodiesel reactor on python using GEKKO, but I'm not getting obtain the correct temperature profile, the Manipulated Variable doesn't change the value as expected... Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
m = GEKKO()
nt = 1001
m.time = np.linspace(0,100,nt)
# Parameters
T = m.MV(value=321,ub=338,lb=298)
T.STATUS = 1
T.DCOST = 0.01
T.DMAX = 20
# Variables
CTG = m.Var(value=0.3226)
CDG = m.Var(value=0)
CMG = m.Var(value=0)
CE = m.Var(value=0)
CA = m.Var(value=1.9356)
CG = m.Var(value=0)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Intermediates
k1 = m.Intermediate(3.92e7*m.exp(-6614.83/T))
k2 = m.Intermediate(5.77e5*m.exp(-4997.98/T))
k3 = m.Intermediate(5.88e12*m.exp(-9993.96/T))
k4 = m.Intermediate(0.98e10*m.exp(-7366.64/T))
k5 = m.Intermediate(5.35e3*m.exp(-3231.18/T))
k6 = m.Intermediate(2.15e4*m.exp(-4824.87/T))
# Equations
m.Equation(CTG.dt()== -k1*CTG*CA + k2*CDG*CE)
m.Equation(CDG.dt()== k1*CTG*CA - k2*CDG*CE - k3*CDG*CA + k4*CMG*CE)
m.Equation(CMG.dt()== k3*CDG*CA - k4*CMG*CE - k5*CMG*CA + k6*CG*CE)
m.Equation(CE.dt()== k1*CTG*CA - k2*CDG*CE + k3*CDG*CA - k4*CMG*CE + k5*CMG*CA - k6*CG*CE)
m.Equation(CA.dt()== -CE.dt())
m.Equation(CG.dt()== k5*CMG*CA - k6*CG*CE)
# Objective Function
m.Maximize(CE*final)
m.options.IMODE = 9
m.solve()
print('Objective: ' + str(CE[-1]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,CE.value,'k:',lw=2,label=r'$C_E$')
plt.plot(m.time,CTG.value,'b-',lw=2,label=r'$C_TG$')
plt.plot(m.time,CA.value,'g-',lw=2,label=r'$C_A$')
plt.ylabel('Value')
plt.legend(loc='best')
plt.figure(2)
plt.subplot(2,1,2)
plt.plot(m.time,T.value,'r--',lw=2,label=r'$Temp$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
I need to obtain a temperature profile like this:
A sensitivity study reveals that the optimal solution is to maximize the temperature. The problem with the code in your question is the selection of IMODE=9. Use IMODE=6 with less time points for a more reliable solution. The sequential optimization mode (9) is not as reliable as the simulataneous mode (6).
Here is a simulation at different temperatures with the temperature status off.
T.STATUS = 0
The simulation sensitivity study shows that there is an effect of temperature on the final objective. Using T.STATUS=1 with different initial conditions also reveals that higher temperature gives a more optimal solution. The only effect of the initial condition is that it requires one or two steps to get up to the maximum allowable temperature.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
m = GEKKO()
nt = 101
m.time = np.linspace(0,100,nt)
# Parameters
T = m.MV(value=321,ub=338,lb=298)
T.STATUS = 1
T.DCOST = 0 #0.01
T.DMAX = 20
# Variables
CTG = m.Var(value=0.3226)
CDG = m.Var(value=0)
CMG = m.Var(value=0)
CE = m.Var(value=0)
CA = m.Var(value=1.9356)
CG = m.Var(value=0)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Intermediates
k1 = m.Intermediate(3.92e7*m.exp(-6614.83/T))
k2 = m.Intermediate(5.77e5*m.exp(-4997.98/T))
k3 = m.Intermediate(5.88e12*m.exp(-9993.96/T))
k4 = m.Intermediate(0.98e10*m.exp(-7366.64/T))
k5 = m.Intermediate(5.35e3*m.exp(-3231.18/T))
k6 = m.Intermediate(2.15e4*m.exp(-4824.87/T))
# Equations
m.Equation(CTG.dt()== -k1*CTG*CA + k2*CDG*CE)
m.Equation(CDG.dt()== k1*CTG*CA - k2*CDG*CE - k3*CDG*CA + k4*CMG*CE)
m.Equation(CMG.dt()== k3*CDG*CA - k4*CMG*CE - k5*CMG*CA + k6*CG*CE)
m.Equation(CE.dt()== k1*CTG*CA - k2*CDG*CE + k3*CDG*CA - k4*CMG*CE + k5*CMG*CA - k6*CG*CE)
m.Equation(CA.dt()== -CE.dt())
m.Equation(CG.dt()== k5*CMG*CA - k6*CG*CE)
# Objective Function
m.Maximize(CE*final)
m.options.IMODE=6
m.options.NODES=3
m.options.SOLVER=3
plt.figure()
m.options.TIME_SHIFT=0
for Ti in [300,305,310,315,320,325,330,335]:
T.value=Ti
m.solve(disp=False)
plt.subplot(2,1,1)
plt.plot(m.time,CE.value,lw=2,label=r'$C_E$ # $T_0$='+str(Ti))
plt.subplot(2,1,2)
plt.plot(m.time,T.value,lw=2,label=r'$T$ # $T_0$='+str(Ti))
print(Ti,'K Objective: ' + str(CE[-1]))
plt.ylabel('Value')
plt.legend(loc='best')
plt.xlabel('Time')
plt.show()
I would recommend that you feed in the optimal solution that comes from the publication as an initial guess to see if that improves the optimization result. There may be a difference in equations or objective function that gives the different results.
Related
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO()
m.options.SOLVER = 1
m.options.IMODE = 3
Num_car = 1
TOU = [64.9,64.9,64.9,64.9,64.9,64.9,64.9,64.9,152.6,239.8,239.8,152.6,239.8,239.8,239.8,239.8,152.6,152.6,152.6,152.6,152.6,152.6,152.6,64.9]
n=len(TOU)
p_i = m.Array(m.Var,(n,Num_car))
input = m.Array(m.Var, (n), value = 0.0, lb = 0.0, ub = 7.0, integer = True)
SOC_t = m.Array(m.Var,(n, Num_car))
for tt in range(0,n):
for i in range(0,Num_car):
SOC_t[tt,i].lower = 30
SOC_t[tt,i].upper = 70
SOC_t[0,0] = 30
eq_car_bat = np.zeros((n))
eq_car_bat = list(eq_car_bat)
for tt in range(0,n):
eq_car_bat[tt] = SOC_t[tt] + input[tt] == p_i[tt]
m.Equation(eq_car_bat)
SOC_Max = 90
SOC_Min = 30
sum_soc = sum(SOC_t[tt])
eq_total = np.zeros((n))
eq_total = list(eq_total)
eq_total = sum_soc == SOC_Max
m.Equation(eq_total)
for i in range(n):
m.Minimize(TOU[i]*p_i[i])
m.options.IMODE = 3
m.options.SOLVER = 1
m.solve(disp=True)
I want to find minimized charging costs when the EV charging.
My code is shown below but, I received the following error "x must be a python list of GEKKO parameters, variables, or expressions" that I don't know how to solve.
The intention appears to be to decide when to charge a battery over a 24 hour time period given a specific Time of Use (TOU). Here is a modified version of the script that charges (integer values 0-7) from 30 to 70 on the SOC. It has the capability to add additional vehicles but there is currently only one.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO()
m.options.SOLVER = 1
m.options.IMODE = 3
Num_car = 1
TOU = [64.9,64.9,64.9,64.9,64.9,64.9,64.9,64.9,152.6,239.8,
239.8,152.6,239.8,239.8,239.8,239.8,152.6,152.6,
152.6,152.6,152.6,152.6,152.6,64.9]
n=len(TOU)
inp = m.Array(m.Var, (n), value = 0.0,
lb = 0.0, ub = 7.0, integer = True)
SOC_Min = 30; SOC_Max = 90
# set bounds 30-90
SOC_t = m.Array(m.Var,(n, Num_car),lb=SOC_Min,ub=SOC_Max)
# set new bounds 30-70
for tt in range(0,n):
for j in range(Num_car):
SOC_t[tt,j].lower = 30
SOC_t[tt,j].upper = 70
for j in range(Num_car):
# initial SOC
m.Equation(SOC_t[0,j]==30) # initial charge at start
m.Equation(SOC_t[n-1,j]==70) # desired charge at end
for tt in range(1,n):
m.Equation(SOC_t[tt,j] == SOC_t[tt-1,j] + inp[tt])
for tt in range(n):
m.Minimize(TOU[tt]*inp[tt])
m.options.IMODE = 3
m.options.SOLVER = 1
m.solve(disp=True)
plt.figure(figsize=(8,5))
plt.subplot(3,1,1)
for j in range(Num_car):
p = np.empty(n)
for tt in range(n):
p[tt] = SOC_t[tt,j].value[0]
plt.plot(p,'r.-',label='vehicle '+str(j+1))
plt.legend(); plt.ylabel('SOC'); plt.grid()
plt.subplot(3,1,2)
p = np.empty(n)
for tt in range(n):
p[tt] = inp[tt].value[0]
plt.plot(p,'ko-',label='charge rate')
plt.legend(); plt.ylabel('charge'); plt.grid()
plt.subplot(3,1,3)
plt.plot(TOU,'bs-',label='electricity price')
plt.ylabel('price'); plt.grid()
plt.legend(); plt.xlabel('Time (hr)')
plt.tight_layout()
plt.savefig('soc_results.png',dpi=300)
plt.show()
This problem is related to some of the other problems in the Energy Benchmarks. Gekko has the capability to manage the time aspect of the problem when using IMODE=6 instead of indexing time explicitly. Using IMODE=3 (default) gives more control over the problem structure. IMODE=6 is better if there are differential equations.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO() # initialize gekko
nt=200
m.time = np.linspace(0,1,nt)
# Variables
x1 = m.Var(value=20,lb=0,)
x2 = m.Var(value=0,lb=0,)
x3 = m.Var(value=0,lb=0,)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
# optimize final time
tf = m.FV(value=0.7,lb=0.001,ub=100.0)
tf.STATUS = 1
# Equations
m.Equation(x1.dt() == -5*x1*tf)
m.Equation(x2.dt() == (5* x1 - 0.5*x2)*tf)
m.Equation(x3.dt() == 0.5*x2*tf)
#Minifunc
#m.Minimize(tf)
m.Maximize(x2)
#options
m.options.IMODE = 6
m.options.SOLVER = 3 #IPOPT optimizer
m.options.RTOL = 1E-8
m.options.OTOL = 1E-8
m.options.NODES = 5
m.solve(disp=True,debug=1)
print('Final Time: ' + str(tf.value[0]))
#Plotting stuff
tm = np.linspace(0,tf.value[0],nt)
plt.figure(1) # plot results
plt.plot(tm, x1.value, "k-", label=r"$x_1$")
plt.plot(tm, x2.value, "b-", label=r"$x_2$")
plt.plot(tm, x3.value, "r--", label=r"$x_3$")
plt.legend(loc="best")
plt.xlabel("Time")
plt.ylabel("Value")
plt.show()
I am trying to get used to Gekko as an optimization tool for some kinetic data. Therefore, I tried to implement a simple ode system based on an example of the Gekko website. Nevertheless, it never found the global maximum unless I force the derivative to be dx2/dt>0 to get the right maximum. This is somewhat unsatisfying, as I would expect a solver to find the maximum of component x2 just by asking to find the Maximum via m.Maximize(x2) for such a simple system. Is there any setting I did wrong or is my solution badly proposed? Thanks for helping.
The result: shows clearly that the maximum is not found...
The unconstrained objective (without the constraint dx2dt>=0) is 10416.8 while the objective with the constraint is 9236.4 (worse). Additional constraints never improve the optimization result but can make the objective worse for convex optimization problems such as this one.
m.Equation(x2.dt() >= 0)
Here is the code that shows the results of adding the constraint:
import numpy as np
from gekko import GEKKO
import matplotlib.pyplot as plt
m = GEKKO() # initialize gekko
nt=200
m.time = np.linspace(0,1,nt)
# Variables
x1 = m.Var(value=20,lb=0,)
x2 = m.Var(value=0,lb=0,)
x3 = m.Var(value=0,lb=0,)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
# optimize final time
tf = m.FV(value=0.7,lb=0.001,ub=100.0)
tf.STATUS = 1
# Equations
m.Equation(x1.dt() == -5*x1*tf)
m.Equation(x2.dt() == (5* x1 - 0.5*x2)*tf)
m.Equation(x3.dt() == 0.5*x2*tf)
#Minifunc
#m.Minimize(tf)
m.Maximize(x2)
#options
m.options.IMODE = 6
m.options.SOLVER = 1 #APOPT optimizer
m.options.RTOL = 1E-8
m.options.OTOL = 1E-8
m.options.NODES = 5
m.solve(disp=True,debug=1)
plt.figure(1)
tm = np.linspace(0,tf.value[0],nt)
plt.plot(tm, x2.value, "b:", label=r"$x_2$ Optimal")
# add constraint
m.Equation(x2.dt() >= 0)
m.options.TIME_SHIFT=0
m.solve(disp=True,debug=1)
tm = np.linspace(0,tf.value[0],nt)
plt.plot(tm, x2.value, "r--", label=r"$x_2$ Constrained")
plt.legend(loc="best"); plt.xlabel("Time"); plt.ylabel("Value")
print('Final Time: ' + str(tf.value[0]))
plt.figure(2) # plot results
plt.plot(tm, x1.value, "k-", label=r"$x_1$")
plt.plot(tm, x2.value, "b-", label=r"$x_2$")
plt.plot(tm, x3.value, "r--", label=r"$x_3$")
plt.legend(loc="best"); plt.xlabel("Time"); plt.ylabel("Value")
plt.show()
The current objective statement is to maximize the value of x2 everywhere.
m.Maximize(x2)
Even though there is a maximum part-way through the simulation, the final objective is the summation of all the x2 values. Make the following changes to maximize only the final x2 point.
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(p)
m.Maximize(x2*final)
The optimal solution is now just the final point for x2 that is 15.485.
I am starting to learn Gekko and I am testing optimal control problems. I am trying to solve the following optimal control problem with Gekko
The solution of this problem is (x_1(t) = (t-2)^2 - 2)
How to build the constraint x(0) + x(2) = 0?
My code gives me a wrong solution.
m = GEKKO(remote=False) # initialize gekko
nt = 101
m.time = np.linspace(0,2,nt)
#end_loc = nt-1
# Variables
x1 = m.CV(fixed_initial=False)
x2 = m.CV(fixed_initial=False)
x3 = m.Var(value=0)
#u = m.Var(value=0,lb=-2,ub=2)
u = m.MV(fixed_initial=False,lb=-2,ub=2)
u.STATUS = 1
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
p1 = np.zeros(nt)
p1[0] = 1.0
p1[-1] = 1.0
infin = m.Param(value=p1)
# Equations
m.Equation(x1.dt()==x2)
m.Equation(x2.dt()==u)
m.Equation(x3.dt()==x1)
# Constraints
m.Equation(infin*x1==0)
m.Equation(final*x2==0)
m.Obj(x3*final) # Objective function
#m.fix(x2,pos=end_loc,val=0.0)
m.options.IMODE = 6 # optimal control mode
#m.solve(disp=True) # solve
m.solve(disp=False) # solve
plt.figure(1) # plot results
plt.plot(m.time,x1.value,'k-',label=r'$x_1$')
plt.plot(m.time,x2.value,'b-',label=r'$x_2$')
plt.plot(m.time,x3.value,'g-',label=r'$x_3$')
plt.plot(m.time,u.value,'r--',label=r'$u$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
plt.figure(1) # plot results
plt.plot(m.time,x1.value,'k-',label=r'$x_1$')
plt.plot(m.time,(m.time-2)**2-2,'g--',label=r'$\hat x_1$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
Use m.integral or m.vsum() to create a time weighted summation or vertical summation along the time direction. Here is a solution that replicates the exact solution.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=True) # initialize gekko
nt = 501
m.time = np.linspace(0,2,nt)
# insert a small time step at the beginning and end
# for final and initial condition equation x(1e-8)+x(2-1e-8)=0
# this doesn't change the numerical solution significantly
m.time = np.insert(m.time,1,1e-8)
m.time = np.insert(m.time,len(m.time)-1,2.0-1e-8)
nt += 2
# Variables
x1 = m.Var(fixed_initial=False,lb=-100,ub=100)
x2 = m.Var(fixed_initial=False)
u = m.MV(fixed_initial=False,lb=-2,ub=2)
u.STATUS = 1
p = np.zeros(nt) # mark final time point
p[-2] = 1.0
final = m.Param(value=p)
q = p.copy()
q[1] = 1.0
final_initial = m.Param(value=q)
xfi = m.Var()
m.Equation(xfi==final_initial*x1)
# Equations
m.Equation(x1.dt()==x2)
m.Equation(x2.dt()==u)
m.Equation(final*m.vsum(xfi)==0)
m.Equation(final*x2==0)
m.Minimize(m.integral(x1)*final) # Objective function
m.options.IMODE = 6 # optimal control mode
m.options.NODES = 2
m.solve(disp=True) # solve
plt.figure(1) # plot results
plt.subplot(2,1,1)
plt.plot(m.time,x1.value,'k-',label=r'$x_1$')
plt.plot(m.time,x2.value,'b-',label=r'$x_2$')
plt.plot(m.time,u.value,'--',color='orange',label=r'$u$')
plt.legend(loc='best')
plt.ylabel('Value')
plt.subplot(2,1,2)
plt.plot(m.time,x1.value,'k-',label=r'$x_1$')
plt.plot(m.time,(m.time-2)**2-2,'r--',label=r'$\hat x_1$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
One issue is that x1(1-e8)+x1(2-1e-8)=0 is used as a constraint instead of x1(0)+x1(2)=0. The numerical solutions should be nearly equivalent and the 1e-8 can be further reduced.
I want to use two inputs or more to create a more precise estimation of a variable. I already estimated it using only one input and one FOPDT equation, but when I try to add one more input and the respective k, tau and theta, along with another equation, i get "Solution Not Found" error. Can I create a system of equations this way?
More details about the solver output below. Even though I added more variables than equations to use more than one input, this made my problem have negative degrees of freedom.
--------- APM Model Size ------------
Each time step contains
Objects : 2
Constants : 0
Variables : 15
Intermediates: 0
Connections : 4
Equations : 6
Residuals : 6
Number of state variables: 1918
Number of total equations: - 2151
Number of slack variables: - 0
---------------------------------------
Degrees of freedom : -233
* Warning: DOF <= 0
**********************************************
Dynamic Estimation with Interior Point Solver
**********************************************
Info: Exact Hessian
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************
This is Ipopt version 3.12.10, running with linear solver ma57.
Number of nonzeros in equality constraint Jacobian...: 6451
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 1673
Exception of type: TOO_FEW_DOF in file "IpIpoptApplication.cpp" at line 891:
Exception message: status != TOO_FEW_DEGREES_OF_FREEDOM evaluated false: Too few degrees of freedom (rethrown)!
EXIT: Problem has too few degrees of freedom.
An error occured.
The error code is -10
---------------------------------------------------
Solver : IPOPT (v3.12)
Solution time : 2.279999999154825E-002 sec
Objective : 0.000000000000000E+000
Unsuccessful with error code 0
---------------------------------------------------
Creating file: infeasibilities.txt
Use command apm_get(server,app,'infeasibilities.txt') to retrieve file
#error: Solution Not Found
And here's the code
from gekko import GEKKO
import numpy as np
import pandas as pd
import plotly.express as px
d19jc = d19jc.dropna()
d19jcSlice = d19jc.loc['2019-10-22 05:30:00':'2019-10-22 09:30:00'] #jc22
d19jcSlice.index = pd.to_datetime(d19jcSlice.index)
d19jcSliceGroupMin = d19jcSlice.groupby(pd.Grouper(freq='T')).mean()
data = d19jcSliceGroupMin.dropna()
xdf1 = data['Cond_PID_SP']
xdf2 = data['Front_PID_SP']
ydf1 = data['Cond_Center_Top_TC']
xms1 = pd.Series(xdf1)
xm1 = np.array(xms1)
xms2 = pd.Series(xdf2)
xm2 = np.array(xms2)
yms = pd.Series(ydf1)
ym = np.array(yms)
xm_r = len(xm1)
tm = np.linspace(0,xm_r-1,xm_r)
m = GEKKO()
m.options.IMODE=5
m.time = tm; time = m.Var(0); m.Equation(time.dt()==1)
k1 = m.FV(lb=0.1,ub=5); k1.STATUS=1
tau1 = m.FV(lb=1,ub=300); tau1.STATUS=1
theta1 = m.FV(lb=0,ub=30); theta1.STATUS=1
k2 = m.FV(lb=0.1,ub=5); k2.STATUS=1
tau2 = m.FV(lb=1,ub=300); tau2.STATUS=1
theta2 = m.FV(lb=0,ub=30); theta2.STATUS=1
# create cubic spline with t versus u
uc1 = m.Var(xm1); tc1 = m.Var(tm); m.Equation(tc1==time-theta1)
m.cspline(tc1,uc1,tm,xm1,bound_x=False)
# create cubic spline with t versus u
uc2 = m.Var(xm2); tc2 = m.Var(tm); m.Equation(tc2==time-theta2)
m.cspline(tc2,uc2,tm,xm2,bound_x=False)
x1 = m.Param(value=xm1)
x2 = m.Param(value=xm2)
y = m.Var(value=ym)
yObj = m.Param(value=ym)
m.Equation(tau1*y.dt()+(y-ym[0])==k1 * (uc1-xm1[0]))
m.Equation(tau2*y.dt()+(y-ym[0])==k2 * (uc2-xm2[0]))
m.Minimize((y-yObj)**2)
m.options.EV_TYPE=2
print('solve start')
m.solve(disp=True)
print('k1: ', k1.value[0])
print('tau1: ', tau1.value[0])
print('theta1: ', theta1.value[0])
print('k2: ', k2.value[0])
print('tau2: ', tau2.value[0])
print('theta2: ', theta2.value[0])
df_plot = pd.DataFrame({'DateTime' : data.index,
'Cond_Center_Top_TC' : np.array(yObj),
'Fit Cond_Center_Top_TC - Train' : np.array(y),
figGekko = px.line(df_plot,
x='DateTime',
y=['Cond_Center_Top_TC','Fit Cond_Center_Top_TC - Train'],
labels={"value": "Degrees Celsius"},
title = "(Cond_PID_SP & Front_PID_SP) -> Cond_Center_Top_TC JC only - Train")
figGekko.update_layout(legend_title_text='')
figGekko.show()
You currently have only one variable and two equations.
y = m.Var(value=ym)
yObj = m.Param(value=ym)
m.Equation(tau1*y.dt()+(y-ym[0])==k1 * (uc1-xm1[0]))
m.Equation(tau2*y.dt()+(y-ym[0])==k2 * (uc2-xm2[0]))
You need to create two separate variables y1 and y2 for the two equations.
y1 = m.Var(value=ym)
y2 = m.Var(value=ym)
yObj = m.Param(value=ym)
m.Equation(tau1*y1.dt()+(y1-ym[0])==k1 * (uc1-xm1[0]))
m.Equation(tau2*y2.dt()+(y2-ym[0])==k2 * (uc2-xm2[0]))
You may also need to create two separate measurement vectors for ym1 and ym2 if you have different data for each.
Edit: Multiple Input, Single Output (MISO) System
For a MISO system, you need to adjust the equation as shown in the Process Dynamics and Control course.
y = m.Var(value=ym)
yObj = m.Param(value=ym)
m.Equation(tau*y.dt()+(y-ym[0])==k1 * (uc1-xm1[0]) + k2 * (uc2-xm2[0]))
There is only one time constant in this form. If they need different time constants, you can also add the two outputs together with:
y1 = m.Var(value=ym[0])
y2 = m.Var(value=ym[0])
y = m.Var(value=ym)
yObj = m.Param(value=ym)
m.Equation(tau1*y1.dt()+(y1-ym[0])==k1 * (uc1-xm1[0]))
m.Equation(tau2*y2.dt()+(y2-ym[0])==k2 * (uc2-xm2[0]))
m.Equation(y==y1+y2)
In this case, y is the variable that has data and y1 and y2 are unmeasured states.
Please see the below example code for a FOPDT model that has two inputs and one output. Both the transfer function models have different FOPDT parameters, including different deadtimes as well.
It is still in a dynamic simulation mode (IMODE=4), but you can start from this modifying a little bit toward the dynamic estimation mode (IMODE=5) and MPC mode (IMODE=6) later.
from gekko import GEKKO
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
tf = 100
npt = 101
t = np.linspace(0,tf,npt)
u1 = np.zeros(npt)
u2 = np.zeros(npt)
u1[10:] = 5
u2[40:] = -5
m = GEKKO(remote=True)
m.time = t
time = m.Var(0)
m.Equation(time.dt()==1)
K1 = m.FV(1,lb=0,ub=1); K1.STATUS=1
tau1 = m.FV(5, lb=1,ub=300); tau1.STATUS=1
theta1 = m.FV(10, lb=2,ub=30); theta1.STATUS=1
K2 = m.FV(0.5,lb=0,ub=1); K2.STATUS=1
tau2 = m.FV(10, lb=1,ub=300); tau2.STATUS=1
theta2 = m.FV(20, lb=2,ub=30); theta2.STATUS=1
uc1 = m.Var(u1)
uc2 = m.Var(u2)
tc1 = m.Var(t)
tc2 = m.Var(t)
m.Equation(tc1==time-theta1)
m.Equation(tc2==time-theta2)
m.cspline(tc1,uc1,t,u1,bound_x=False)
m.cspline(tc2,uc2,t,u2,bound_x=False)
yp = m.Var()
yp1 = m.Var()
yp2 = m.Var()
m.Equation(yp1.dt() == -yp1/tau1 + K1*uc1/tau1)
m.Equation(yp2.dt() == -yp2/tau2 + K2*uc2/tau2)
m.Equation(yp == yp1 + yp2)
m.options.IMODE=4
m.solve()
print('K1: ', K1.value[0])
print('tau1: ', tau1.value[0])
print('theta1: ', theta1.value[0])
print('')
print('K2: ', K2.value[0])
print('tau2: ', tau2.value[0])
print('theta2: ', theta2.value[0])
plt.figure()
plt.subplot(2,1,1)
plt.plot(t,u1)
plt.plot(t,u2)
plt.legend([r'u1', r'u2'])
plt.ylabel('Inputs 1 & 2')
plt.subplot(2,1,2)
plt.plot(t,yp)
plt.legend([r'y1'])
plt.ylabel('Output')
plt.xlabel('Time')
plt.savefig('sysid.png')
plt.show()
K1: 1.0
tau1: 5.0
theta1: 10.0
K2: 0.5
tau2: 10.0
theta2: 20.0
I copied and pasted the code from https://apmonitor.com/pdc/index.php/Main/ArduinoEstimation2?action=sourceblock&num=12 and I commented out the filename='data.csv' so that it would use the information on the apmonitor website. The graph that it is producing is very different from the one posted. I tried doing gekko(remote=True) and also gekko(remote=false) but it gave the same issue. I also tried clearing the cache and restarting everything but it did the same thing. Did I copy and paste something wrong? Is there an issue with the Gekko server right now?
You may need to upgrade your Gekko version:
pip install gekko --upgrade
Here is the solution that I get with both remote=False (local computed) and remote=True (cloud computed):
U : 5.3262633524
Us : 6.8780901241
alpha1: 0.011754836102
alpha2: 0.006022948462
tau: 18.916289169
SAE Energy Balance: 499.55
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from gekko import GEKKO
from scipy.integrate import odeint
from scipy.interpolate import interp1d
# Import data
filename = 'https://apmonitor.com/pdc/uploads/Main/tclab_data2.txt'
data = pd.read_csv(filename)
# Fit Parameters of Energy Balance
m = GEKKO() # Create GEKKO Model
# Parameters to Estimate
U = m.FV(value=10,lb=1,ub=20)
Us = m.FV(value=20,lb=5,ub=40)
alpha1 = m.FV(value=0.01,lb=0.001,ub=0.03) # W / % heater
alpha2 = m.FV(value=0.005,lb=0.001,ub=0.02) # W / % heater
tau = m.FV(value=10.0,lb=5.0,ub=60.0)
# Measured inputs
Q1 = m.Param()
Q2 = m.Param()
Ta =23.0+273.15 # K
mass = 4.0/1000.0 # kg
Cp = 0.5*1000.0 # J/kg-K
A = 10.0/100.0**2 # Area not between heaters in m^2
As = 2.0/100.0**2 # Area between heaters in m^2
eps = 0.9 # Emissivity
sigma = 5.67e-8 # Stefan-Boltzmann
TH1 = m.SV()
TH2 = m.SV()
TC1 = m.CV()
TC2 = m.CV()
# Heater Temperatures in Kelvin
T1 = m.Intermediate(TH1+273.15)
T2 = m.Intermediate(TH2+273.15)
# Heat transfer between two heaters
Q_C12 = m.Intermediate(Us*As*(T2-T1)) # Convective
Q_R12 = m.Intermediate(eps*sigma*As*(T2**4-T1**4)) # Radiative
# Energy balances
m.Equation(TH1.dt() == (1.0/(mass*Cp))*(U*A*(Ta-T1) \
+ eps * sigma * A * (Ta**4 - T1**4) \
+ Q_C12 + Q_R12 \
+ alpha1*Q1))
m.Equation(TH2.dt() == (1.0/(mass*Cp))*(U*A*(Ta-T2) \
+ eps * sigma * A * (Ta**4 - T2**4) \
- Q_C12 - Q_R12 \
+ alpha2*Q2))
# Conduction to temperature sensors
m.Equation(tau*TC1.dt() == TH1-TC1)
m.Equation(tau*TC2.dt() == TH2-TC2)
# Options
# STATUS=1 allows solver to adjust parameter
U.STATUS = 1
Us.STATUS = 1
alpha1.STATUS = 1
alpha2.STATUS = 1
tau.STATUS = 1
Q1.value=data['Q1'].values
Q2.value=data['Q2'].values
TH1.value=data['T1'].values[0]
TH2.value=data['T2'].values[0]
TC1.value=data['T1'].values
TC1.FSTATUS = 1 # minimize fstatus * (meas-pred)^2
TC2.value=data['T2'].values
TC2.FSTATUS = 1 # minimize fstatus * (meas-pred)^2
m.time = data['Time'].values
m.options.IMODE = 5 # MHE
m.options.EV_TYPE = 2 # Objective type
m.options.NODES = 2 # Collocation nodes
m.options.SOLVER = 3 # IPOPT
m.solve(disp=False) # Solve
# Parameter values
print('U : ' + str(U.value[0]))
print('Us : ' + str(Us.value[0]))
print('alpha1: ' + str(alpha1.value[0]))
print('alpha2: ' + str(alpha2.value[-1]))
print('tau: ' + str(tau.value[0]))
sae = 0.0
for i in range(len(data)):
sae += np.abs(data['T1'][i]-TC1.value[i])
sae += np.abs(data['T2'][i]-TC2.value[i])
print('SAE Energy Balance: ' + str(sae))
# Create plot
plt.figure(figsize=(10,7))
ax=plt.subplot(2,1,1)
ax.grid()
plt.plot(data['Time'],data['T1'],'r.',label=r'$T_1$ measured')
plt.plot(m.time,TC1.value,color='black',linestyle='--',\
linewidth=2,label=r'$T_1$ energy balance')
plt.plot(data['Time'],data['T2'],'b.',label=r'$T_2$ measured')
plt.plot(m.time,TC2.value,color='orange',linestyle='--',\
linewidth=2,label=r'$T_2$ energy balance')
plt.ylabel(r'T ($^oC$)')
plt.legend(loc=2)
ax=plt.subplot(2,1,2)
ax.grid()
plt.plot(data['Time'],data['Q1'],'r-',\
linewidth=3,label=r'$Q_1$')
plt.plot(data['Time'],data['Q2'],'b:',\
linewidth=3,label=r'$Q_2$')
plt.ylabel('Heaters')
plt.xlabel('Time (sec)')
plt.legend(loc='best')
plt.savefig('tclab_2nd_order_physics.png')
plt.show()