I am trying to simulate 3D plane flight. I have an issue with gamma value (Flight-path angle). It gets out of its bounds and then, the simulation stops. The gamma value is being calculated by this equation:
I turned it into this: m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))
The target of the simulation is for the plane to reach certain X an Y values(m.Minimize(w*final*(x-pathx)**2) and m.Minimize(w*final*(pathy-y)**2)), while minimizing fuel consumed m.Maximize(0.2*mass*tf*final).
The solver controls gamma value, by controlling lift coefficient Cl, which affects the lift value L, which, in turn, affects gamma value. The equation that calculates lift L value looks like this: m.Equation(L==0.5*Ro*(V**2)*(Cl)*S). But in the end the solver can not control gamma value till the plane gets to its destination.
What could be messing with it?
My code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import math
#Gekko model
m = GEKKO(remote=False)
#Time points
nt = 11
tm = np.linspace(0,100,nt)
m.time = tm
# Variables
Ro = m.Var(value=1.1)#air density
g = m.Const(value=9.80665)
pressure = m.Var(value=101325)#
T = m.Var(value=281,lb=100)#temperature
T0 = m.Const(value=288)#temperature at see level
S = m.Const(value=122.6)
Cd = m.Var(value=0.025)#drag coef 0.06 works
#Cl = m.Const(value=0.3)#lift couef
FuelFlow = m.Var()
D = m.Var(value=10000,lb=0)#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()
V = m.Var(value=200,lb=45,ub=240)#velocity
gamma = m.Var(value=0,lb=-0.6,ub=1.2)# Flight-path angle
#gammaa = gamma.value
Xi = m.Var(value=0, lb=-2, ub=2.0)# Heading angle
x = m.Var(value=0,lb=-1000,ub=1015000)#x position
y = m.Var(value=0,lb=-1000,ub=1011000)#y position
h = m.Var(value=1000,lb=-20000,ub=50000)# height
targetH = m.Param(value=10000) #target flight altitude
mass = m.Var(value=60000,lb=10000)
pathx = m.Const(value=1000000) #intended distance in x direction
pathy = m.Const(value=1000000) #intended distance in y direction
L = m.Var(value=250000)#lift
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=10000 # iteration number
#Fixed Variable
tf = m.FV(value=1,lb=0.0001,ub=100.0)#
tf.STATUS = 1
# Controlled parameters
Tcontr = m.MV(value=0.6,lb=0.0,ub=1)# solver controls throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
Mu = m.MV(value=0,lb=-1,ub=1)# solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
Cl = m.MV(value=0.1,lb=-0.3,ub=0.9)# solver controls lift couef
Cl.STATUS = 1
Cl.DCOST = 1e-3
# Equations
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60000)))#
m.Equation(V.dt()==tf*((Thr-D-mass*g*m.cos(gamma))/mass)) #
m.Equation(x.dt()==tf*(V*(m.cos(gamma))*(m.cos(Xi))))#
m.Equation(x*final<=pathx)
#pressure and density part
m.Equation(T==T0-(0.0065*h))
m.Equation(pressure==101325*(1-(0.0065*h)/T0)**((g*0.0289652)/(8.31446*0.0065)))#
m.Equation(Ro*(8.31446*T)==(pressure*0.0289652))
#2D addition part
m.Equation(L==0.5*Ro*(V**2)*(Cl)*S)# Problem here or no problem, idk
m.Equation(mass*m.cos(gamma)*V*Xi.dt()==tf*((L*m.sin(Mu)))) #
m.Equation(y.dt()==tf*(V*(m.cos(gamma))*(m.sin(Xi))))#
#m.Equation((y-pathy)*final==0)
m.Equation(y*final<=pathy)
#3D addition part
m.Equation(h.dt()==tf*(V*(m.sin(gamma))))#
m.Equation(h*final<=targetH)
m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))#
#Cd equation
m.Equation(Cd==((Cl)**2)/10)
# Objective Function
w = 1e4
m.Minimize(w*final*(x-pathx)**2) #1D part (x)
m.Minimize(w*final*(pathy-y)**2) #2D part (y)
m.Maximize(0.2*mass*tf*final) #objective function
m.options.IMODE = 6
m.options.NODES = 2 # it was 3 before
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
fig, axs = plt.subplots(11)
fig.suptitle('Results')
axs[0].plot(tm,Tcontr,'r-',LineWidth=2,label=r'$Tcontr$')
axs[0].legend(loc='best')
axs[1].plot(tm,V.value,'b-',LineWidth=2,label=r'$V$')
axs[1].legend(loc='best')
axs[2].plot(tm,x.value,'r--',LineWidth=2,label=r'$x$')
axs[2].legend(loc='best')
axs[3].plot(tm,D.value,'g-',LineWidth=2,label=r'$D$')
axs[3].legend(loc='best')
axs[4].plot(tm,L.value,'p-',LineWidth=2,label=r'$L$')
axs[4].legend(loc='best')
axs[5].plot(tm,y.value,'p-',LineWidth=2,label=r'$y$')
axs[5].legend(loc='best')
axs[6].plot(tm,Ro.value,'p-',LineWidth=2,label=r'$Ro$')
axs[6].legend(loc='best')
axs[7].plot(tm,h.value,'p-',LineWidth=2,label=r'$h$')
axs[7].legend(loc='best')
axs[8].plot(tm,gamma.value,'p-',LineWidth=2,label=r'$gamma$')
axs[8].legend(loc='best')
axs[9].plot(tm,Cl.value,'p-',LineWidth=2,label=r'$Cl$')
axs[9].legend(loc='best')
axs[10].plot(tm,Cd.value,'p-',LineWidth=2,label=r'$Cd$')
axs[10].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
Important update
I want the solver to be able to keep gamma value within its boundaries till the necessary x and y values are achieved. The solver has issues with that.
Exhibit A.
In this case the simulation stopped because it could not keep gamma from exceeding its lower bound. gamma usually gets bigger when L (lift) * cos(mu) is bigger than mass*g*cos(gamma), and its gets lower when the situation is the opposite: mass*g*cos(gamma) is bigger than the L (lift) * cos(mu). Then the question becomes: why did suddenly mass*g*cos(gamma) became so much bigger than the L (lift) * cos(mu)? The g was constant. The mass did not change that much during the last moments of the simulation. The L (lift) did not become particularly small. cos(mu) is usually equal to 1 during this part of the simulation.
Exhibit B.
In this case the simulation stopped, once again, because it could not keep gamma value within its upper bound. It is visible , that Cl value during the last moments of the simulation was rising, it was necessary to keep gamma from exceeding its lower bound, but after that, the gamma value spiked and, for some reason, the solver did not lower Cl value, which forced the gamma value to exceed its upper bound, which forced the simulation to stop before the target goals are achieved.
In this case the issue is: Why does doesn't the solver lower the Cl value to stop gamma from exceeding its upper bound?
I'm not sure exactly what the problem is. It looks like the optimizer is controlling gamma throughout the horizon, and it always stays within its bounds of -0.6 to 1.2. Can you provide more information about what is going wrong?
max(gamma.value)
>>>> 1.2
min(gamma.value)
>>>> -0.6
Here is the solution that I generated by running your code:
Note that you get a fairly different solution if you solve the problem with a higher time resolution:
I am optimizing plane flight using optimal control. The plane flies certain distance (path variable) and then the simulation stops. The solver is trying to minimize the fuel consumption m.Maximize(mass*tf*final), by maximizing the mass value.
I have added 2 solver controlled variables:
throttle control, like this:
Tcontr = m.MV(value=0.2,lb=0.2,ub=1)
Tcontr.STATUS = 1
Tcontr.DCOST = 0
and simulation time, like this:
tf = m.FV(value=1,lb=0.0001,ub=1000.0)#
tf.STATUS = 1
And the system worked as intended.
After that, I tried to implement one more controlled variable, bank angle control, that looks like this:
Mu = m.MV(value=0,lb=-1.5,ub=1.5)
Mu.STATUS = 1
Mu.DCOST = 0
But for some reason, the program says that "Mu" is not defined.
How do I define Mu solver controlled variable?
How do I define next solver controlled variables?
My code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import math
#Gekko model
m = GEKKO(remote=False)
#Time points
nt = 11
tm = np.linspace(0,100,nt)
m.time = tm
# Variables
Ro = m.Var(value=1.1)#air density
g = m.Const(value=9.80665)
pressure = m.Var(value=101325)#
T = m.Var(value=281)#temperature
T0 = m.Const(value=288)#temperature at see level
S = m.Const(value=122.6)
Cd = m.Const(value=0.1)#drag coef
Cl = m.Var(value=1)#lift couef
FuelFlow = m.Var()
D = m.Var()#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()
V = m.Var(value=100,lb=0,ub=240)#velocity
#Vmin = m.Var(value=100)
gamma = m.Var(value=0)# Flight-path angle
gammaa = gamma.value
Xi = m.Var(value=0)# Heading angle
Xii = Xi.value
#Mu = m.Var()# Bank angle (controlled var)
Muu = Mu.value
#AOA = m.Var()#angle of attack (not needed atm)
x = m.Var(value=0,lb=0)#x position
y = m.Var(value=0,lb=0)#y position
h = m.Var(value=1000)# height
mass = m.Var(value=60000)
path = m.Const(value=5000) #intended distance length
L = m.Var()#lift
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=10000 # iteration number
#Fixed Variable
tf = m.FV(value=1,lb=0.0001,ub=1000.0)#
tf.STATUS = 1
# Controlled parameters
Tcontr = m.MV(value=0.2,lb=0.2,ub=1)# solver controls throttle pedal
Tcontr.STATUS = 1
Tcontr.DCOST = 0
Mu = m.MV(value=0,lb=-1.5,ub=1.5)# solver controls bank angle - does not work
Mu.STATUS = 1
Mu.DCOST = 0
# Equations
m.Equation(x.dt()==tf*(V*(math.cos(gammaa.value))*(math.cos(Xii.value))))#
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(V.dt()==tf*((Thr-D)/mass))#
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60000)))#
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation(x*final<=path)
#pressure and density part(density isnt working)
m.Equation(T==T0-(0.0065*h))
m.Equation(pressure==101325*(1-(0.0065*h)/T0)**((g*0.0289652)/(8.31446*0.0065)))# equation works
#m.Equation(Ro==(pressure*0.0289652)/(8.31446*T))
#2D addition part
m.Equation(y.dt()==tf*(V*(math.cos(gamma.value))*(math.sin(Xii.value))))#
m.Equation(Xi.dt()==tf*((L*math.sin(Muu))/(mass*V)))
m.Equation(L==0.5*Ro*(V**2)*Cl*S)
#3D addition part
# Objective Function
m.Minimize(final*(x-path)**2) #1D part
m.Maximize(mass*tf*final) #objective function
m.options.IMODE = 6
m.options.NODES = 2 # it was 3 before
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
fig, axs = plt.subplots(6)
fig.suptitle('Results')
axs[0].plot(tm,Tcontr,'r-',LineWidth=2,label=r'$Tcontr$')
axs[0].legend(loc='best')
axs[1].plot(tm,V.value,'b-',LineWidth=2,label=r'$V$')
axs[1].legend(loc='best')
axs[2].plot(tm,x.value,'r--',LineWidth=2,label=r'$x$')
axs[2].legend(loc='best')
axs[3].plot(tm,D.value,'g-',LineWidth=2,label=r'$D$')
axs[3].legend(loc='best')
axs[4].plot(tm,mass.value,'g:',LineWidth=2,label=r'$mass$')
axs[4].legend(loc='best')
axs[5].plot(tm,T.value,'p-',LineWidth=2,label=r'$T$')
axs[5].legend(loc='best')
#axs[6].plot(tm,Ro.value,'p-',LineWidth=2,label=r'$Ro$')
#axs[6].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
So, I found out what was at fault. The variable Muu that references variable Mu must be defined after the variable Mu, like this:
Mu = m.MV(value=0)
Mu.STATUS = 1
Mu.DCOST = 0
Muu = Mu.value
Not like this:
Muu = Mu.value
Mu = m.MV(value=0)
Mu.STATUS = 1
Mu.DCOST = 0
I am trying to produce a model of the motion of a cyclist which optimises the time taken to complete a 12km hill time trial, subject to various constraints such as total anaerobic energy as max power. I am attempting to reproduce the results in "Road Cycling Climbs Made Speedier by Personalized Pacing Strategies" by Wolf et al. The model so far is described by the code below: where Pow is the cyclist power, tf is the time taken to complete the course, E_kin is the kinetic energy of the cyclist, E_an is the anaerobic energy reserve (initially E_an_init) which decreases when the cyclist outputs a power higher than P_c, s is the gradient of the course and mu is the rolling resistance constant, eta is the mechanical efficiency of the bike and C_p is the aerodynamic drag coefficient. I am currently using IMODE 6 but I've also tried 9. When currently running the code I get an error saying the solution could not be found and I am not sure why this is occurring, I've tried checking the infeasibilities file but I can't make sense. I would appreciate some pointers as to where I might be going wrong. This is my first time using Gekko so I assume it may be something related to my implementation of the model.
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False) # initialize gekko
nt = 501
m.time = np.linspace(0,1,nt)
#Parameters, taken from paper, idepdendant of the path
#mass = 80
g = 9.81
mu = 0.004
r_w = 0.335
I_s = 0.658
M = 80#mass + (I_s/r_w**2)
C_p = 0.7
rho = 1.2
A = 0.4
eta = 0.95
P_c = 150
P_max = 1000
#Path dependant
x_f = 12000
s = 0.081
#Define anaerobic energy reserve, dependant on the cyclist
E_an_init = 20000
#Variables
xpos = m.Var(value = 0, lb = 0,ub = x_f,name='xpos')
E_kin = m.Var(value = 0, lb=0, name='E_kin')#start at 1m/s
E_an = m.Var(value = E_an_init, lb=0 , ub=E_an_init, name='E_an')
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# optimize final time
tf = m.FV(value=1,lb = 0, name='tf')
tf.STATUS = 1
# control changes every time period
Pow = m.MV(value=0,lb=0,ub=P_max, name='Pow')
Pow.STATUS = 1
# Equations
m.Equation(xpos.dt()==((2*E_kin/M)**0.5)*tf)
m.Equation(E_kin.dt()==(eta*Pow-((2*E_kin/M)**0.5)*(M*g*(s+mu*m.cos(m.atan(s))) + C_p*rho*A*E_kin/M))*tf)
m.Equation(E_an.dt()==(P_c-Pow)*tf)
m.Equation(xpos*final == x_f)
m.Equation(E_an*final >= 0)
#m.options.MAX_ITER = 1000
m.Minimize(tf*final) # Objective function
m.options.IMODE = 6 # optimal control mode
m.open_folder() # open folder if remote=False to see infeasibilities.txt
m.solve(disp=True) # solve
print('Final Time: ' + str(tf.value[0]) + "s")
The results of the failed attempt look okay when plotted but it says that that the 12km are covered in 48.6 seconds which can't be correct when the velocity of the cyclist is around 20km/hr for most of the 12km. Seems like the results are a function of nt, which makes no sense. Moreover, the velocity of the cyclist derived from dx/dt doesn't match that derived from the kinetic energy equation.
I have used this solution as a reference so far:
https://apmonitor.com/do/index.php/Main/MinimizeFinalTime
You need to reform the final equation for position. Otherwise, it is 0==12000 for all but the final point. The original form gives an infeasible solution.
m.Equation(xpos*final == x_f)
Here is the correct form of that equation.
m.Equation(final*(xpos-x_f)==0)
In this form, it is 0==0 everywhere except at the end where it is 1*(xpos-12000)==0. It also helps to give an objective function term to guide the final condition with:
m.Minimize(final*(xpos-x_f)**2)
This guides the solver towards the feasible solution to meet the end point.
The inequality constraint
m.Equation(E_an*final >= 0)
isn't needed if E_an has a lower bound of zero with E_an = m.Var(lb=0) or by setting E_an.LOWER=0. It should always be positive throughout the entire time horizon, not just at the final solution. It is easier for the solver to have a constraint on the variable (e.g. lower bound) than to add an additional inequality constraint (e.g. with m.Equation()).
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO() # initialize gekko
nt = 101
m.time = np.linspace(0,1,nt)
#Parameters, taken from paper, idepdendant of the path
#mass = 80
g = 9.81
mu = 0.004
r_w = 0.335
I_s = 0.658
M = 80 #mass + (I_s/r_w**2)
C_p = 0.7
rho = 1.2
A = 0.4
eta = 0.95
P_c = 150
P_max = 1000
#Path dependant
x_f = 12000
s = 0.081
#Define anaerobic energy reserve, dependant on the cyclist
E_an_init = 20000
#Variables
xpos = m.Var(value = 0, lb = 0,ub = x_f,name='xpos')
E_kin = m.Var(value = 0, lb=1e-3, name='E_kin')#start at 1m/s
E_an = m.Var(value = E_an_init, lb=0 , ub=E_an_init, name='E_an')
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# optimize final time
tf_guess = 900
tf = m.FV(value=tf_guess,lb=300, ub=10000, name='tf')
tf.STATUS = 1
# control changes every time period
Pow = m.MV(value=100,lb=0,ub=P_max, name='Pow')
Pow.STATUS = 1
# Equations
Esr = m.Intermediate((2*E_kin/M)**0.5)
m.Equation(xpos.dt()==Esr*tf)
m.Equation(E_kin.dt()==(eta*Pow-Esr*(M*g*(s+mu*m.cos(m.atan(s))) \
+ C_p*rho*A*E_kin/M))*tf)
m.Equation(E_an.dt()==(P_c-Pow)*tf)
m.Minimize(final*(xpos-x_f)**2)
m.Equation(final*(xpos-x_f)==0)
m.Minimize(final*(tf**2))
m.options.IMODE = 6 # optimal control mode
m.solve(disp=True) # solve
print('Final Time: ' + str(tf.value[0]) + "s")
print('Final Position: ' + str(xpos.value[-1]))
print('Final Energy: ' + str(E_an.value[-1]))
t = m.time*tf.value[0]/60.0
plt.figure(figsize=(9,6))
plt.subplot(4,1,1)
plt.plot(t,xpos.value,'r-',label='xpos')
plt.legend()
plt.subplot(4,1,2)
plt.plot(t,E_kin.value,'b-',label='E_kin')
plt.legend()
plt.subplot(4,1,3)
plt.plot(t,E_an.value,'k-',label='E_an')
plt.legend()
plt.subplot(4,1,4)
plt.plot(t,Pow.value,'-',color='orange',label='Pow')
plt.legend()
plt.xlabel('Time (min)')
plt.show()
The solution doesn't look quite right for a competitive cyclist to cover 12 km. It should be much faster so there may be a problem with the current equations. The final position constraint is met and the final energy is zero but the time should be faster.
Final Time: 5550.6961268s
Final Position: 12000.0
Final Energy: 0.0
I'm trying to implement a thermostat control using MPC and GEKKO.
The state variable (temperature) should be within a lower and upper pre-specified temp values , temp_low and temp_upper in the code below.
Both bound vary along the day: one value per hour.
The objective function is the cost-to-go of using the heating. The price also changes along the day, TOU below. T_external is the room's exterior temperature that plays a role in the differential equation.
How can implement this so that it optimizes?
This is my attempt:
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False)
m.time = np.linspace(0,23,24)
#initialize variables
T_external = [50.,50.,50.,50.,45.,45.,45.,60.,60.,63.,64.,45.,45.,50.,52.,53.,53.,54.,54.,53.,52.,51.,50.,45.]
temp_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.]
temp_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,200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,0.05,0.05,0.05]
b = m.Param(value=1.)
k = m.Param(value=0.05)
T_e = m.Param(value=T_external)
u = m.MV(value=[0]*24, lb=[0.0]*24, ub=[1.]*24)
u.STATUS = 1 # allow optimizer to change
# Controlled Variable
T = m.SV(value=[60]*24, lb=temp_low, ub=temp_upper)
m.Equation(T.dt() == k*(T_e-T) + b*u)
m.Obj(np.dot(TOU,u))
m.options.IMODE = 6
m.solve(debug=True)
When I run this I get:
#error: Model Expression
*** Error in syntax of function string: Missing operator
Position: 4
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
?
Gekko needs the constaints as inequality expressions where the variable T is compared to the upper TH or lower TL values. If you have b=1., it leads to an infeasible solution because the heater isn't powerful enough to maintain the temperature within the upper and lower limits. I changed the value to b=10 to get a feasible solution.
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False)
m.time = np.linspace(0,23,24)
#initialize variables
T_external = [50.,50.,50.,50.,45.,45.,45.,60.,60.,63.,\
64.,45.,45.,50.,52.,53.,53.,54.,54.,\
53.,52.,51.,50.,45.]
temp_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.]
temp_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_v = [0.05,0.05,0.05,0.05,0.05,0.05,0.05,200.,200.,200.,200.,\
200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,0.05,\
0.05,0.05]
b = m.Param(value=10.)
k = m.Param(value=0.05)
T_e = m.Param(value=T_external)
TL = m.Param(value=temp_low)
TH = m.Param(value=temp_upper)
TOU = m.Param(value=TOU_v)
u = m.MV(lb=0, ub=1)
u.STATUS = 1 # allow optimizer to change
# Controlled Variable
T = m.SV(value=60)
m.Equations([T>=TL,T<=TH])
m.Equation(T.dt() == k*(T_e-T) + b*u)
m.Minimize(TOU*u)
m.options.IMODE = 6
m.solve(disp=True,debug=True)
A potentially better solution is to set up soft constraints by redefining the limits as an error. You can minimize the error to stay within the limits. Even if it can't stay within the limits, the optimizer will do the best it can to minimize the infeasibility. This also allows you to trade-off multiple objectives simultaneously such as between comfort and cost.
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False)
m.time = np.linspace(0,23,24)
#initialize variables
T_external = [50.,50.,50.,50.,45.,45.,45.,60.,60.,63.,\
64.,45.,45.,50.,52.,53.,53.,54.,54.,\
53.,52.,51.,50.,45.]
temp_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.]
temp_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_v = [0.05,0.05,0.05,0.05,0.05,0.05,0.05,200.,200.,200.,200.,\
200.,200.,200.,200.,200.,200.,200.,200.,200.,200.,0.05,\
0.05,0.05]
b = m.Param(value=10.)
k = m.Param(value=0.05)
T_e = m.Param(value=T_external)
TL = m.Param(value=temp_low)
TH = m.Param(value=temp_upper)
TOU = m.Param(value=TOU_v)
u = m.MV(lb=0, ub=1)
u.STATUS = 1 # allow optimizer to change
# Controlled Variable
T = m.SV(value=60)
# Soft constraints
eH = m.CV(value=0)
eL = m.CV(value=0)
eH.SPHI=0; eH.WSPHI=100; eH.WSPLO=0 ; eH.STATUS = 1
eL.SPLO=0; eL.WSPHI=0 ; eL.WSPLO=100; eL.STATUS = 1
m.Equations([eH==T-TH,eL==T-TL])
m.Equation(T.dt() == k*(T_e-T) + b*u)
m.Minimize(TOU*u)
m.options.IMODE = 6
m.solve(disp=True,debug=True)
import matplotlib.pyplot as plt
plt.subplot(2,1,1)
plt.plot(m.time,temp_low,'k--')
plt.plot(m.time,temp_upper,'k--')
plt.plot(m.time,T.value,'r-')
plt.ylabel('Temperature')
plt.subplot(2,1,2)
plt.step(m.time,u.value,'b:')
plt.ylabel('Heater')
plt.xlabel('Time (hr)')
plt.show()