I am currently using Python.
However, I am struggling with one error.
This is the tool I have made so far.
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False)
m.options.SOLVER = 1
hour = 24
Num_EV = 1
p_i =m.Array(m.Var,(hour,Num_EV))
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)
# EV min/Max setting
for tt in range(0,hour):
p_i[tt,0].lower = 30
p_i[tt,0].upper = 70
# EV Charger min/Max setting
Num_EV_C = 1
p_j = m.Array(m.Var, (hour, Num_EV_C))
for tt in range(0,hour):
p_j[tt,0].lower = 0
p_j[tt,0].upper = 7
# s.t : EV SOC
p_i[0,0] = 30 # inital EV SOC
eq_EV_SOC = np.zeros((hour,1))
eq_EV_SOC = list(eq_EV_SOC)
for tt in range(0,hour):
for i in range(0,Num_EV):
eq_EV_SOC[tt] = p_i[tt-1,i] + p_i[tt,i] == p_i[tt,0]
m.Equation(eq_EV_SOC)
# s.t : EV charging rate
p_j[0,0] = 0
eq_EV_C = np.zeros((hour,1))
eq_EV_C = list(eq_EV_C)
for tt in range(0,hour):
for i in range(0,Num_EV_C):
eq_EV_C[tt] = p_j[tt,0] >= p_j[tt,i]
m.Equation(eq_EV_C)
# Object Function : sum[i=n]*sum[t=T]()
F = np.zeros((hour*Num_EV))
F = F.tolist()
for tt in range(0,hour):
for i in range(0,Num_EV):
F[i+tt*Num_EV] = p_i[tt,i] * p_j[tt,i]
F_Obj = m.sum(F)
m.Minimize(F_Obj)
m.solve(disp=True)
Exception: #error: Equation Definition
Equation without an equality (=) or inequality (>,<) true STOPPING...
I want to know this problem.
Below is a description of constraints and objective functions.
s.t is constraint. First constraint is EV SOC range. EV SOC minimum is 30 and Maxmium is 70. EV SOC form is (inital SOC + time by EV SOC). Second constraint is EV Charging range. EV Charging range is from 0 to 7.
Finally, Object function is to minimize the product of tou and charging rate.
There are a few problems with the model that can be observed by opening the model file in the run directory. Use m.open_folder() and open the gk_model0.apm file with a text editor. Here are some of the equations that indicate that there is a problem with the formulation:
True
v50>=v50
v51>=v51
v52>=v52
v53>=v53
The True expression is because a constant is evaluated with another constant in the first cycle of:
for tt in range(0,hour):
for i in range(0,Num_EV_C):
eq_EV_C[tt] = p_j[tt,0] >= p_j[tt,i]
This gives a Boolean True result.
The initial EV SOC should either be changed to fixed or else include a simple equation:
# s.t : EV SOC
m.Equation(p_i[0,0]== 30) # inital EV SOC
# s.t : EV charging rate
m.Equation(p_j[0,0]==0)
It appears that the charging rate should decrease over time with this constraint:
m.Equation(p_j[0,0]==0)
for tt in range(0,hour):
for i in range(1,Num_EV_C):
m.Equation(p_j[tt,0] >= p_j[tt,i])
The index is changed to p_j[tt,0] >= p_j[tt,i] so that p_j[tt,0] >= p_j[tt,0] is not included as an equation. Should the range for time also be adjusted here to start at 1?
for tt in range(1,hour):
for i in range(0,Num_EV):
m.Equation(p_i[tt-1,i] + p_i[tt,i] == p_i[tt,0])
The problem is currently infeasible, even with these corrections. Maybe this problem can help:
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 is a solution to this question: How to optimize the electric vehicle charging cost using Gekko? It looks like you may be working on a similar problem.
Here are additional similar questions:
How to model a time-dependent constraint in Gekko?
GEKKO RTO vs MPC MODES
Mixed-Integer Model Predictive Control using Gekko
Variable bounds in MPC with GEKKO
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.
Problem statement:
Trying to utilize GEKKO to optimally select the number of appliances within the available capacity while minimizing the energy cost.
Problem model:
I cannot post an image but here is the link to see the formula and also you can copy and paste below to find it visually better:
\min \sum _{t=1}^{24} n x_{i} P_{i}
subject to:
\sum _{i=1}^{24} C_{i} - x_{i} = 0
C_{min} < C_{i}< C_{max}
x_{min} < x_{i}< x_{max}
n_{min} < n_{i}< n_{max}
\sum_{t=1}^{T} \tau_{i}^{t} = I_{i}
where,
x and n are the state variables representing the assigned power and number of appliances in a station. C is the capacity, 𝜏 is the operating state of appliance (the ON/OFF condition) at time slot t, and I_{i} is the permissible interval for each appliances (i.e. 5 hrs)
Work done so far:
Thanks to post here I put the following together:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=False)
m.time = np.linspace(0,23,24)
#initialize variables
cap_low = [55.,55.,55.,55.,55.,55.,55.,68.,68.,68.,68.,\
55.,55.,68.,68.,68.,68.,55.,55.,55.,55.,55.,55.,55.]
cap_upper = [75.,75.,75.,75.,75.,75.,75.,70.,70.,70.,70.,\
75.,75.,70.,70.,70.,70.,75.,75.,75.,75.,75.,75.,75.]
TOU = [0.05,0.05,0.05,0.05,0.05,0.05,0.05,50.,50.,50.,50.,50.,\
50.,50.,50.,50.,50.,50.,50.,50.,50.,0.05,0.05,0.05]
k = 1.1 #Safety factor
CL = m.Param(value=cap_low)
CH = m.Param(value=cap_upper)
TOU = m.Param(value=TOU)
x = m.MV(lb=6, ub=32)
x.STATUS = 1 # allow optimizer to change
n = m.MV(lb=1, ub=10, integer=True)
n.STATUS = 1 # allow optimizer to change
# Controlled Variable
C = m.SV(value=55)
m.Equations([C>=CL, C<=CH])
m.Equation(k*C - n* x == 0)
m.Minimize(TOU*n*x)
m.options.IMODE = 6
m.solve(disp=True,debug=True)
plt.subplot(3,1,1)
plt.plot(m.time,cap_low,'k--')
plt.plot(m.time,cap_upper,'k--')
plt.plot(m.time,C.value,'r-')
plt.ylabel('Demand')
plt.subplot(3,1,2)
plt.step(m.time,x.value,'b:')
plt.ylabel('Load')
plt.xlabel('Time (hr)')
plt.subplot(3,1,3)
plt.step(m.time,n.value,'r:')
plt.ylabel('No. of Appliances')
plt.xlabel('Time (hr)')
plt.show()
Outcome
The above code results in a promising result but there is a lack of last constraint \sum_{t=1}^{T} \tau_{i}^{t} = I_{i} for limiting the operation time of each appliances.
Question:
This problem seems like a combination of Model Predictive Control (tracking the available load capacity) and a Mixed Integer Linear Programing (multiple appliances with multiple linear operational equations). So the questions are:
How can I define the permissible interval for each appliances in above code to limit their operation in a day.
What is the best way to integrate MPC and MILP in Gekko
Answer to question 2: Switch to the APOPT solver to solve MILP or MINLP problems.
Answer to question 1: Separate n into the individual appliance state with:
# decide when to turn on each appliance
na = 20 # number of appliances
a = m.Array(m.MV,na,lb=0, ub=1, integer=True)
for ai in a:
ai.STATUS = 1
ai.DCOST = 0
# number of appliances running
n = m.Var(lb=1, ub=5)
m.Equation(n==m.sum(a))
The bottom subplot shows when the individual appliances are on or off. Limiting the appliance to 5 hours per day is causing problems with convergence:
ax = [m.Intermediate(m.integral(ai)) for ai in a]
for ai in ax:
# maximum of 5 hours each day
m.Minimize(1e-4*ax**2)
m.Equation(ax<=5)
Perhaps you could start with this code and try other strategies to improve convergence such as different APOPT solver options or form of the constraint. Creating a soft constraint (penalize with a financial incentive instead of a hard limit) can also help to improve convergence.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=True)
m.time = np.linspace(0,23,24)
#initialize variables
cap_low = [55.,55.,55.,55.,55.,55.,55.,68.,68.,68.,68.,\
55.,55.,68.,68.,68.,68.,55.,55.,55.,55.,55.,55.,55.]
cap_upper = [75.,75.,75.,75.,75.,75.,75.,70.,70.,70.,70.,\
75.,75.,70.,70.,70.,70.,75.,75.,75.,75.,75.,75.,75.]
TOU = [0.05,0.05,0.05,0.05,0.05,0.05,0.05,50.,50.,50.,50.,50.,\
50.,50.,50.,50.,50.,50.,50.,50.,50.,0.05,0.05,0.05]
k = 1.1 #Safety factor
CL = m.Param(value=cap_low)
CH = m.Param(value=cap_upper)
TOU = m.Param(value=TOU)
x = m.MV(lb=6, ub=32)
x.STATUS = 1 # allow optimizer to change
# decide when to turn on each appliance
na = 20 # number of appliances
a = m.Array(m.MV,na,lb=0, ub=1, integer=True)
for ai in a:
ai.STATUS = 1
ai.DCOST = 0
ax = [m.Intermediate(m.integral(ai)) for ai in a]
for ai in ax:
# maximum of 5 hours each day
for ai in ax:
# maximum of 5 hours each day
m.Minimize(1e-4*ai**2)
m.Equation(ai<=5)
# number of appliances running
n = m.Var(lb=1, ub=5)
m.Equation(n==m.sum(a))
# Controlled Variable
C = m.SV(value=55)
m.Equations([C>=CL, C<=CH])
m.Equation(k*C - n* x == 0)
m.Minimize(TOU*n*x)
m.options.IMODE = 6
m.options.NODES = 2
m.options.SOLVER = 1
m.solver_options = ['minlp_gap_tol 1.0e-2',\
'minlp_maximum_iterations 10000',\
'minlp_max_iter_with_int_sol 500',\
'minlp_branch_method 3']
m.solve(disp=True)
plt.subplot(4,1,1)
plt.plot(m.time,cap_low,'k--')
plt.plot(m.time,cap_upper,'k--')
plt.plot(m.time,C.value,'r-')
plt.ylabel('Demand')
plt.subplot(4,1,2)
plt.step(m.time,x.value,'b:')
plt.ylabel('Load')
plt.xlabel('Time (hr)')
plt.subplot(4,1,3)
plt.step(m.time,n.value,'r:')
plt.ylabel('No. of Appliances')
plt.subplot(4,1,4)
for ai in a:
plt.plot(m.time,ai.value)
plt.xlabel('Time (hr)')
plt.show()
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 am trying to use GEKKO on PYTHON to control the level of a CSTR tank while manipulating the inlet flow q. I tried the same problem using a pid controller and it worked. However, on GEKKO the height is not tracking its setpoint. Once I did the doublet test: at a flow rate of 200, the height reached 800 and as I decreased the flowrate to 2, the height was about 0. However, when im putting the height setpoint in GEKKO as 700 or 800, the flowrate is not stopping at 200, it is continuously increasing indefinitely.
Below is my 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=100.0
h_ss=399.83948182
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]
c1=5.0
Ac=10.0
# initial conditions
h0=399.83948182
q0=100.0
m.h= m.CV(value=h0)
m.q=m.MV(value=q0,lb=0,ub=100000)
m.Equation(m.h.dt()==(m.q-c1*m.h**0.5)/Ac)
#MV tuning
m.q.STATUS = 1
m.q.FSTATUS = 0
m.q.DMAX = 100
m.q.DMAXHI = 100
m.q.DMAXLO = -100
#CV tuning
DT = 0.5 # deadband
m.h.STATUS = 1
m.h.FSTATUS = 1
m.h.TR_INIT = 1
m.h.TAU = 1.0
m.options.CV_TYPE = 1
m.options.IMODE = 6
m.options.SOLVER = 3
#%% define CSTR model
def cstr(x,t,u2,Ac):
q=u2
c1=5.0
Ac=10.0
# States (2):
# the height of the tank (m)
h=x[0]
# Parameters:
# Calculate height derivative
dhdt=(q-c1*h**0.5)/Ac
if x[0]>=1000 and dhdt>0:
dh1dt = 0
# Return xdot:
xdot = np.zeros(1)
xdot[0]= dhdt
return xdot
# Time Interval (min)
t = np.linspace(0,20,100)
# 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:50] = 500.0
hsp[100:150]=700.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+1],Ac))
# retrieve measurements
h[i+1]= y[-1][0]
# insert measurement
m.h.MEAS=h[i+1]
# solve MPC
m.solve(disp=True)
m.h.SPHI = hsp[i+1] + DT
m.h.SPLO = hsp[i+1] - DT
# 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)
Mohamad,
If you want to simulate a tank level control, please start with this code with the volumetric flowrate of inlet(qin, MV) and outlet(qout, DV).
m = GEKKO(remote=False)
m.time = [0,0.02,0.04,0.06,0.08,0.1,0.12,0.15,0.2]
h0=50
q0=10
Ac=30 # m2,Cross section Area
m.h= m.CV(value=h0)
m.qin = m.MV(value=q0,lb=0,ub=100)
m.qout = m.Param(value=5)
m.Equation(m.h.dt() == (m.qin - m.qout)/Ac)