Gekko optimal control. How to add 2nd and 3rd solver controlled variables? - python

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

Related

3 Body Problem Outputs a spikey ball rather than an orbital path

I'm trying to solve the 3 body problem with solve_ivp and its runge kutta sim, but instead of a nice orbital path it outputs a spiked ball of death. I've tried changing the step sizes and step lengths all sorts, I have no idea why the graphs are so spikey, it makes no sense to me.
i have now implemented the velocity as was suggested but i may have done it wrong
What am I doing wrong?
Updated Code:
from scipy.integrate import solve_ivp
import numpy as np
import matplotlib.pyplot as plt
R = 150000000 #radius from centre of mass to stars orbit
#G = 1/(4*np.pi*np.pi) #Gravitational constant in AU^3/solar mass * years^2
G = 6.67e-11
M = 5e30 #mass of the stars assumed equal mass in solar mass
Omega = np.sqrt(G*M/R**3.0) #inverse of the orbital period of the stars
t = np.arange(0, 1000, 1)
x = 200000000
y = 200000000
vx0 = -0.0003
vy0 = 0.0003
X1 = R*np.cos(Omega*t)
X2 = -R*np.cos(Omega*t)
Y1 = R*np.sin(Omega*t)
Y2 = -R*np.sin(Omega*t) #cartesian coordinates of both stars 1 and 2
r1 = np.sqrt((x-X1)**2.0+(y-Y1)**2.0) #distance from planet to star 1 or 2
r2 = np.sqrt((x-X2)**2.0+(y-Y2)**2.0)
xacc = -G*M*((1/r1**2.0)*((x-X1)/r1)+(1/r2**2.0)*((x-X2)/r2))
yacc = -G*M*((1/r1**2.0)*((y-Y1)/r1)+(1/r2**2.0)*((y-Y2)/r2)) #x double dot and y double dot equations of motions
#when t = 0 we get the initial contditions
r1_0 = np.sqrt((x-R)**2.0+(y-0)**2.0)
r2_0 = np.sqrt((x+R)**2.0+(y+0)**2.0)
xacc0 = -G*M*((1/r1_0**2.0)*((x-R)/r1_0)+(1/r2_0**2.0)*((x+R)/r2_0))
yacc0 = -G*M*((1/r1_0**2.0)*((y-0)/r1_0)+(1/r2_0**2.0)*((y+0)/r2_0))
#inputs for runge-kutta algorithm
tp = Omega*t
r1p = r1/R
r2p = r2/R
xp = x/R
yp = y/R
X1p = X1/R
X2p = X2/R
Y1p = Y1/R
Y2p = Y2/R
#4 1st ode
#vx = dx/dt
#vy = dy/dt
#dvxp/dtp = -(((xp-X1p)/r1p**3.0)+((xp-X2p)/r2p**3.0))
#dvyp/dtp = -(((yp-Y1p)/r1p**3.0)+((yp-Y2p)/r2p**3.0))
epsilon = x*np.cos(Omega*t)+y*np.sin(Omega*t)
nave = -x*np.sin(Omega*t)+y*np.cos(Omega*t)
# =============================================================================
# def dxdt(x, t):
# return vx
#
# def dydt(y, t):
# return vy
# =============================================================================
def dvdt(t, state):
xp, yp = state
X1p = np.cos(Omega*t)
X2p = -np.cos(Omega*t)
Y1p = np.sin(Omega*t)
Y2p = -np.sin(Omega*t)
r1p = np.sqrt((xp-X1p)**2.0+(yp-Y1p)**2.0)
r2p = np.sqrt((xp-X2p)**2.0+(yp-Y2p)**2.0)
return (-(((xp-X1p)/(r1p**3.0))+((xp-X2p)/(r2p**3.0))),-(((yp-Y1p)/(r1p**3.0))+((yp-Y2p)/(r2p**3.0))))
def vel(t, state):
xp, yp, xv, yv = state
return (np.concatenate([[xv, yv], dvdt(t, [xp, yp]) ]))
p = (R, G, M, Omega)
initial_state = [xp, yp, vx0, vy0]
t_span = (0.0, 1000) #1000 years
result_solve_ivp_dvdt = solve_ivp(vel, t_span, initial_state, atol=0.1) #Runge Kutta
fig = plt.figure()
plt.plot(result_solve_ivp_dvdt.y[0,:], result_solve_ivp_dvdt.y[1,:])
plt.plot(X1p, Y1p)
plt.plot(X2p, Y2p)
Output:
Green is the stars plot and blue remains the velocity
Km and seconds
Years, AU and Solar Masses
You have produced the equation
dv/dt = a(x)
But then you used the acceleration, the derivative of the velocity, as the derivative of the position. This is physically wrong.
You should pass the function
lambda t, xv: np.concantenate([xv[2:], dvdt(xv[:2]) ])
to the solver, with a suitable initial state containing velocity components in addition to the position components.
In the 2-star system with the fixed orbit, the stars have distance 1. This distance, not the distance 0.5 to the center, should enter the computation of the angular velocity.
z_1 = 0.5*exp(2*pi*i*t), z_2 = -z_1 ==> z_1-z_2=2*z_1, abs(z_1-z_2)=1
z_1'' = -GM * (z_1-z_2)/abs(z_1-z_2)^3
-0.5*4*pi^2 = -GM or GM = 2*pi^2
Now insert a satellite into a circular radius at some radius R as if there was only one central mass 2M stationary at the origin
z_3 = R*exp(i*w*t)
z_3'' = -2GM * z_3/abs(z_3)^3
R^3*w^2=2GM
position (R,0), velocity (0,w*R)=(0,sqrt(2GM/R))
In python code
GM = 2*np.pi**2
R = 1.9
def kepler(t,u):
z1 = 0.5*np.exp(2j*np.pi*t)
z3 = u[0]+1j*u[1]
a = -GM*((z3-z1)/abs(z3-z1)**3+(z3+z1)/abs(z3+z1)**3)
return [u[2],u[3],a.real,a.imag]
res = solve_ivp(kepler,(0,17),[R,0,0,2*np.pi*(1/R)**0.5], atol=1e-8, rtol=1e-11)
print(res.message)
This gives a trajectory plot of
The effect of the binary system on the satellite is a continuous sequence of swing-by maneuvers, accelerating the angular speed until escape velocity is reached. With R=1.5 or smaller this happens with the first close encounter of satellite and closest star, so that the satellite is ejected immediately from the system.
Never-the-less, one can still get "spiky-ball" orbits. Setting R=1.6 in the above code, with tighter error tolerances and integrating to t=27 gives the trajectory

How to use Gekko to solve for optimal control for a reusable reentry vehicle

I am seeking to find optimal control (aoa and bank angle) to maximize cross range for a shuttle type reentry vehicle using Gekko. Below is my code currently and I am getting a "Solution not found" with "EXIT: Maximum Number of Iterations Exceeded". The simulation assumes a point mass with a non-rotating earth frame. The EOMS are 6 coupled, non-linear ODEs. I have tried using different solvers, implementing/removing state and control constraints, increasing maximum number of iterations, etc. I am not confident with my setup and implementation of the problem in Gekko and am hoping for some feedback on what I can try next. I have tried to follow the setup and layouts in APMonitor's Example 11. Optimal Control with Integral Objective, Inverted Pendulum Optimal Control, and Example 13. Optimal Control: Minimize Final Time. Solutions I'm seeking are below.
Any help is very much appreciated!
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
import math
pi = math.pi
########################
######FRONT MATTER######
########################
m = GEKKO() # initialize GEKKO
nt = 2501 #simulation time is 2500 seconds
tfin = 2500
m.time = np.linspace(0,tfin,nt) #time array
#==================#
#PARAMS
#==================#
Re = m.Param(value = 6371203.92) # radius of the earth, m
S = m.Param(value = 249.9091776) # vehicle surface area, m^2
cl0 = m.Param(value = -0.2070) # coeff lift param 1
cl1 = m.Param(value = 1.6756) # coeff lift param 2
cd0 = m.Param(value = 0.0785) # coeff drag param 1
cd1 = m.Param(value = -0.3529) # coeff drag param 2
cd2 = m.Param(value = 2.0400) # coeff drag param 3
H = m.Param(value = 7254.24) # density scale height, m
rho0= m.Param(value = 1.225570827014494) # sea level atmospheric density, kg/m^3
mu = m.Param(value = 3.986031954093051e14) #earth gravitational param, m^3/s^2
mass= m.Param(value = 92079.2525560557) #vehicle mass, kg
#===============================#
#BOUNDARY CONDITIONS
#===============================#
t0 = 0
alt0 = 79248
rad0 = alt0+Re
altf = 24384
radf = altf+Re
lon0 = 0
lat0 = 0
speed0 = +7802.88
speedf = +762
fpa0 = -1*pi/180
fpaf = -5*pi/180
azi0 = +90*pi/180
azif = -90*pi/180
#===============================#
#LIMITS ON VARIABLES
#===============================#
tfMin = 0; tfMax = 3000;
radMin = Re; radMax = rad0;
lonMin = -pi; lonMax = -lonMin;
latMin = -70*pi/180; latMax = -latMin;
speedMin = 10; speedMax = 45000;
fpaMin = -80*pi/180; fpaMax = 80*pi/180;
aziMin = -180*pi/180; aziMax = 180*pi/180;
aoaMin = -90*pi/180; aoaMax = -aoaMin;
bankMin = -90*pi/180; bankMax = 1*pi/180;
#===============================#
#VARIABLES
#===============================#
#state variables and bounds
rad = m.Var(value=rad0, lb=radMin, ub=radMax) # radius, m
lon = m.Var(value=lon0, lb=lonMin, ub=lonMax) # longitude, rad
lat = m.Var(value=lat0, lb=latMin, ub=latMax) # latitude, rad
vel = m.Var(value=speed0, lb=speedMin, ub=speedMax) # velocity, m/sec
fpa = m.Var(value=fpa0, lb=fpaMin, ub=fpaMax) # flight path angle, rad
azi = m.Var(value=azi0, lb=aziMin, ub=aziMax) # azimuth angle, rad
#control variables
aoa = m.MV(value=-20, lb=aoaMin, ub=aoaMax) # angle of attack, rad
aoa.STATUS = 1
aoa.DCOST = 1e-2
bank = m.MV(value=0, lb=bankMin, ub=bankMax) # bank angle, rad
bank.STATUS = 1
bank.DCOST = 1e-2
#===============================#
#INTERMEDIATE VARIABLES
#===============================#
altitude = m.Intermediate(rad - Re)
CD = m.Intermediate(cd0+cd1*aoa+cd2*aoa**2)
rho = m.Intermediate(rho0*m.exp(-altitude/H))
CL = m.Intermediate(cl0+cl1*aoa)
q = m.Intermediate(0.5*rho*vel**2)
D = m.Intermediate(q*S*CD/mass)
L = m.Intermediate(q*S*CL/mass)
gravity = m.Intermediate(mu/rad**2)
#===============================#
#EOMS
#===============================#
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
m.Equation(rad.dt() == vel*m.sin(fpa))
m.Equation((rad*m.cos(lat))*lon.dt() == vel*m.cos(fpa)*m.sin(azi))
m.Equation(rad*lat.dt() == vel*m.cos(fpa)*m.cos(azi))
m.Equation(vel.dt() == -D-gravity*m.sin(fpa))
m.Equation(vel*fpa.dt() == (L*m.cos(bank)-m.cos(fpa)*(gravity-vel**2/rad)))
m.Equation(vel*azi.dt() == (L*m.sin(bank)/m.cos(fpa)+vel**2*m.cos(fpa)*m.sin(azi)*m.tan(lat)/rad))
#===============================#
#OPTIMIZATION SOLVER
#===============================#
m.Obj(-lat*final)
# m.options.SOLVER = 3
# m.options.IMODE = 6
# m.solve(disp=True)
m.options.MAX_ITER = 500
m.options.IMODE = 6
# m.options.NODES = 3
# m.options.MV_TYPE = 1
m.options.SOLVER = 3
# m.open_folder()
m.solve()
The solution should look something like this for altitude, velocity, AOA and bank angle: Altitude Velocity AngleofAttack Bank
I got a successful solution by decreasing the final time (max=0.04 for successful solution) and rearranging the equations to avoid a possible divide-by-zero:
m.Equation(rad.dt() == vel*m.sin(fpa))
m.Equation((rad*m.cos(lat))*lon.dt() == vel*m.cos(fpa)*m.sin(azi))
m.Equation(rad*lat.dt() == vel*m.cos(fpa)*m.cos(azi))
m.Equation(vel.dt() == -D-gravity*m.sin(fpa))
m.Equation(vel*fpa.dt() == (L*m.cos(bank)-m.cos(fpa)*(gravity-vel**2/rad)))
m.Equation(m.cos(fpa)*rad*vel*azi.dt() == \
(L*m.sin(bank)*rad+vel**2*(m.cos(fpa))**2*m.sin(azi)*m.tan(lat)))
Starting with a small time period and observing the states can help to troubleshoot the model.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
import math
pi = math.pi
########################
######FRONT MATTER######
########################
m = GEKKO() # initialize GEKKO
nt = 101 # 2501 #simulation time is 2500 seconds
tfin = 0.04
m.time = np.linspace(0,tfin,nt) #time array
#==================#
#PARAMS
#==================#
Re = m.Param(value = 6371203.92) # radius of the earth, m
S = m.Param(value = 249.9091776) # vehicle surface area, m^2
cl0 = m.Param(value = -0.2070) # coeff lift param 1
cl1 = m.Param(value = 1.6756) # coeff lift param 2
cd0 = m.Param(value = 0.0785) # coeff drag param 1
cd1 = m.Param(value = -0.3529) # coeff drag param 2
cd2 = m.Param(value = 2.0400) # coeff drag param 3
H = m.Param(value = 7254.24) # density scale height, m
rho0= m.Param(value = 1.225570827014494) # sea level atmospheric density, kg/m^3
mu = m.Param(value = 3.986031954093051e14) #earth gravitational param, m^3/s^2
mass= m.Param(value = 92079.2525560557) #vehicle mass, kg
#===============================#
#BOUNDARY CONDITIONS
#===============================#
t0 = 0
alt0 = 79248
rad0 = alt0+Re
altf = 24384
radf = altf+Re
lon0 = 0
lat0 = 0
speed0 = +7802.88
speedf = +762
fpa0 = -1*pi/180
fpaf = -5*pi/180
azi0 = +90*pi/180
azif = -90*pi/180
#===============================#
#LIMITS ON VARIABLES
#===============================#
tfMin = 0; tfMax = 3000;
radMin = Re; radMax = rad0;
lonMin = -pi; lonMax = -lonMin;
latMin = -70*pi/180; latMax = -latMin;
speedMin = 10; speedMax = 45000;
fpaMin = -80*pi/180; fpaMax = 80*pi/180;
aziMin = -180*pi/180; aziMax = 180*pi/180;
aoaMin = -90*pi/180; aoaMax = -aoaMin;
bankMin = -90*pi/180; bankMax = 1*pi/180;
#===============================#
#VARIABLES
#===============================#
#state variables and bounds
rad = m.Var(value=rad0, lb=radMin, ub=radMax) # radius, m
lon = m.Var(value=lon0, lb=lonMin, ub=lonMax) # longitude, rad
lat = m.Var(value=lat0, lb=latMin, ub=latMax) # latitude, rad
vel = m.Var(value=speed0, lb=speedMin, ub=speedMax) # velocity, m/sec
fpa = m.Var(value=fpa0, lb=fpaMin, ub=fpaMax) # flight path angle, rad
azi = m.Var(value=azi0, lb=aziMin, ub=aziMax) # azimuth angle, rad
#control variables
aoa = m.MV(value=-20, lb=aoaMin, ub=aoaMax) # angle of attack, rad
aoa.STATUS = 1
bank = m.MV(value=0, lb=bankMin, ub=bankMax) # bank angle, rad
bank.STATUS = 1
#===============================#
#INTERMEDIATE VARIABLES
#===============================#
altitude = rad - Re
CD = cd0+cd1*aoa+cd2*aoa**2
rho = rho0*m.exp(-altitude/H)
CL = cl0+cl1*aoa
q = 0.5*rho*vel**2
D = q*S*CD/mass
L = q*S*CL/mass
gravity = mu/rad**2
#===============================#
#EOMS
#===============================#
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
m.Equation(rad.dt() == vel*m.sin(fpa))
m.Equation((rad*m.cos(lat))*lon.dt() == vel*m.cos(fpa)*m.sin(azi))
m.Equation(rad*lat.dt() == vel*m.cos(fpa)*m.cos(azi))
m.Equation(vel.dt() == -D-gravity*m.sin(fpa))
m.Equation(vel*fpa.dt() == (L*m.cos(bank)-m.cos(fpa)*(gravity-vel**2/rad)))
m.Equation(m.cos(fpa)*rad*vel*azi.dt() == \
(L*m.sin(bank)*rad+vel**2*(m.cos(fpa))**2*m.sin(azi)*m.tan(lat)))
#===============================#
#OPTIMIZATION SOLVER
#===============================#
m.Maximize(lat*final)
m.options.SOLVER = 3
m.options.IMODE = 6
m.solve(disp=True)
plt.subplot(4,2,1)
plt.plot(m.time,rad.value,label='rad')
plt.legend()
plt.subplot(4,2,2)
plt.plot(m.time,lon.value,label='lon')
plt.legend()
plt.subplot(4,2,3)
plt.plot(m.time,lat.value,label='lat')
plt.legend()
plt.subplot(4,2,4)
plt.plot(m.time,vel.value,label='vel')
plt.legend()
plt.subplot(4,2,5)
plt.plot(m.time,fpa.value,label='fpa')
plt.legend()
plt.subplot(4,2,6)
plt.plot(m.time,azi.value,label='azi')
plt.legend()
plt.subplot(4,2,7)
plt.plot(m.time,aoa.value,label='aoa')
plt.xlabel('Time')
plt.legend()
plt.subplot(4,2,8)
plt.plot(m.time,bank.value,label='bank')
plt.xlabel('Time')
plt.legend()
plt.show()
A suggestion is to turn off the degrees of freedom to verify the solution (STATUS=0) with smaller time horizons. Additional constraints may also be needed to keep the trigonometric functions in the -2pi to 2pi region. There is additional information on initializing challenging problems:
Safdarnejad, S.M., Hedengren, J.D., Lewis, N.R., Haseltine, E., Initialization Strategies for Optimization of Dynamic Systems, Computers and Chemical Engineering, 2015, Vol. 78, pp. 39-50, DOI: 10.1016/j.compchemeng.2015.04.016.
Success!! Thank you for your feedback and pointers #John Hedengren. After incorporating what I learned here and reinstating the bounds on the states I was able successfully use Gekko to produce an answer consistent with previous solutions. Very excited about Gekko and the potential here!

GEKKO. X value does not go beyond certain point

I need to solve 1D plane flight optimal control problem. I have a plane that is 1000m high. I need it to travel certain distance (x) forward along x-axis while minimizing fuel consumption. And when it achieves travels that distance x I need program to stop. This function controls it: m.Equation(x*final<=1500).
And for some reason during the simulation my x value does not want to go higher than 1310.
How could I fix that "blockage"?
My gekko script:
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,1,nt)
m.time = tm
# Variables
Ro = m.Const(value=1.1)
g = m.Const(value=9.80665)
T = m.Var()
T0 = m.Const(value=273)
S = m.Const(value=122.6)
Cd = m.Const(value=0.1)
FuelFlow = m.Var()
D = m.Var()
Thrmax = value=200000
Thr = m.Var()
V = m.Var()
nu = m.Var(value=0)
nuu = nu.value
x = m.Var(value=0)
h = m.Var(value=1000)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=40000 # iteration max number
#Fixed Variable
tf = m.FV(value=1,lb=0.1,ub=1000.0)
tf.STATUS = 1
# Parameters
Tcontr = m.MV(value=0.2,lb=0.2,ub=1)
Tcontr.STATUS = 1
Tcontr.DCOST = 0 #1e-2
# Equations
m.Equation(x.dt()==tf*(V*(math.cos(nuu.value))))#
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(V.dt()==tf*((Thr-D)/mass))#
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60)))#
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation(x*final<=1500)
m.Equation(T==T0-h)
# Objective Function
m.Obj(-mass*tf*final)#
m.options.IMODE = 6
m.options.NODES = 3
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
print('Final Time: ' + str(tf.value[-1]))
print('Final Speed: ' + str(V.value[-1]))
print('Final X: ' + str(x.value[-1]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(tm,Tcontr,'r-',LineWidth=2,label=r'$Tcontr$')
#plt.plot(m.time,x.value,'r--',LineWidth=2,label=r'$x$')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(tm,x.value,'r--',LineWidth=2,label=r'$x$')
#plt.plot(tm,mass.value,'g:',LineWidth=2,label=r'$mass$')
#plt.plot(tm,D.value,'g:',LineWidth=2,label=r'$D$')
#plt.plot(tm,V.value,'b-',LineWidth=2,label=r'$V$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
The lower bound on Tcontr is preventing the x value from going above 1310. Setting the lower value to 0.1 improves the final value of x to 2469.76 if the constraint m.Equation(x*final<=1500) is removed.
Tcontr = m.MV(value=0.2,lb=0.1,ub=1)
Constraints are often the culprit when the solution is not optimal or there is an infeasible solution. One way to detect problematic constraints is to create plots to verify that the solution is not artificially bound.
Another way to formulate the constraint is to use a combination of a hard constraint m.Equation((x-1500)*final==0) and a soft constraint to guide the solution as m.Minimize(final*(x-1500)**2). It is important to pose the hard constraint correctly. A constraint such as m.Equation(x*final==1500) means that it is infeasible with x*0==1500 when final is not equal to 1. The inequality version is also better posed as m.Equation((x-1500)*final<=0) but the original form also works because x*0<1500.
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,1,nt)
m.time = tm
# Variables
Ro = m.Const(value=1.1)
g = m.Const(value=9.80665)
T = m.Var()
T0 = m.Const(value=273)
S = m.Const(value=122.6)
Cd = m.Const(value=0.1)
FuelFlow = m.Var()
D = m.Var()
Thrmax = value=200000
Thr = m.Var()
V = m.Var()
nu = m.Var(value=0)
nuu = 0
x = m.Var(value=0)
h = m.Var(value=1000)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=1000 # iteration max number
#Fixed Variable
tf = m.FV(value=1,lb=0.15,ub=1000.0)
tf.STATUS = 1
# Parameters
Tcontr = m.MV(value=0.2,lb=0.1,ub=1)
Tcontr.STATUS = 1
Tcontr.DCOST = 0 #1e-2
# Equations
m.Equation(x.dt()==tf*(V*(math.cos(nuu))))#
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(V.dt()==tf*((Thr-D)/mass))#
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60)))#
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation((x-1500)*final==0)
m.Equation(T==T0-h)
# Objective Function
m.Minimize(final*(x-1500)**2)
m.Maximize(mass*tf*final)#
m.options.IMODE = 6
m.options.NODES = 3
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
print('Final Time: ' + str(tf.value[-1]))
print('Final Speed: ' + str(V.value[-1]))
print('Final X: ' + str(x.value[-1]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(tm,Tcontr,'r-',lw=2,label=r'$Tcontr$')
#plt.plot(m.time,x.value,'r--',lw=2,label=r'$x$')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(tm,x.value,'r--',lw=2,label=r'$x$')
#plt.plot(tm,mass.value,'g:',lw=2,label=r'$mass$')
#plt.plot(tm,D.value,'g:',lw=2,label=r'$D$')
#plt.plot(tm,V.value,'b-',lw=2,label=r'$V$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()

1D Plane flight optimal control problem in gekko

I need to solve 1D plane flight optimal control problem. I have a plane that is 1000m high. I need it to travel 1000m forward along x-axis while minimizing fuel consumption. And when it achieves x=1000 I need program to stop.
My objective function looks like this: min J => -mass(tf). (By maximizing mass, the consumed fuel gets minimized). Optimizer controls the accelerator pedal.
The problem is subject to the following constraints: dx/dt = V; dV/dt = (Throttle - Drag)/mass; dm/dt = -Throttle * fuelflow.
I have developed a Gekko / Python script as follows. However, the optimization script does not achieve a solution, raising:
Exception: #error: Solution Not Found
I have tried to change stuff here and there, but it did not work and I'm not sure what the problem is. Here is 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 = 101
m.time = np.linspace(0,100,nt)
# Parameters
Tcontr = m.MV(value=0.5,lb=0.3,ub=1) #throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
# Variables
Ro = 1.1 #air density
S = 122.6
Cd = value=0.1
FuelFlow = m.Var(value=0.7)
D = m.Var() #drag
Thrmax = 200000 #maximum theoretical throttle
Thr = m.Var() #real throttle
V = m.Var() #Velocity
x = m.Var(value=0)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==V)
m.Equation(Thr==Tcontr*Thrmax) #Throttle
m.Equation(V.dt()==(Thr-D)/mass)
m.Equation(mass.dt()==-Thr*FuelFlow)
m.Equation(D==0.5*Ro*(V**2)*Cd*S) #Drag
m.Equation(final*x==1000) # to stop when x==1000 is achieved
# Objective Function
m.Obj(-mass*final)
m.options.IMODE = 6
m.options.NODES = 2
m.options.MV_TYPE = 1
m.options.SOLVER = 3
m.open_folder()
m.solve()
It seems like the major problem was that the upper bound of your MV (Tcontr) is little bit too low to meet all the constraints.
And, I guess the "FuelFlow" is need to be a m.Const as I assumed it is a fuel flowrate per throttle.
Lastly, when you use the final for designating the value at the last time point, you need to make sure the associated Equation is also feasible for the entire time steps. I suggest to use this instead.
m.Equation(final*(x-1000)==0)
That way, you can achive your goal without posing any infeasible equation as your previous equation did. Your previous equation is infeasible at the most of the time points except the last time point with non-zero value. It's been resulting "0 (LHS) = 1000 (RHS)" before the final time point.
Please see the modified code below.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
# import math
#Gekko model
m = GEKKO(remote=True)
#Time points
nt = 101
m.time = np.linspace(0,100,nt)
# Parameters
Tcontr = m.MV(value=0.5,lb=0.3,ub=10) #throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 1e-2
# Variables
Ro = 1.1 #air density
S = 122.6
Cd = value=0.1
FuelFlow = m.Const(value=0.7)
D = m.Var() #drag
Thrmax = 200000 #maximum theoretical throttle
Thr = m.Var() #real throttle
V = m.Var() #Velocity
x = m.Var(value=0)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==V)
m.Equation(Thr==Tcontr*Thrmax) #Throttle
m.Equation(mass*V.dt()==(Thr-D))
m.Equation(mass.dt()==-Thr*FuelFlow)
m.Equation(D==0.5*Ro*(V**2)*Cd*S) #Drag
m.Equation(final*(x-1000)==0) # to stop when x==1000 is achieved
# Objective Function
m.Obj(-mass*final)
m.options.IMODE = 6
m.options.NODES = 3
m.options.MV_TYPE = 1
m.options.SOLVER = 3
# m.open_folder()
m.solve()

Variable bounds in MPC with GEKKO

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()

Categories