The goal is to minimize time to complete the lap with Energy constraint this is why my objective is the integral of the speed over distance, but I canโt seem to figure out how to derive and integrate over distance and not time(dt).
If you don't have time in your problem then you can specify m.time as the distance points for integration. However, your differential equations are based on time such as ds/dt = v in 1D. You need to keep time as the variable because that is defined for each of the differentials.
One way to minimize the lap time is to create a new tlap=FV() and then scale all of the differentials by that new adjustable value.
tlap=FV()
m.Equation(s.dt()==v*tlap)
With this tf value, you can minimize final time to reach a final destination.
m.Minimize(tf*final)
This is similar to the rocket launch problem that minimizes final time and control action.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
# create GEKKO model
m = GEKKO()
# scale 0-1 time with tf
m.time = np.linspace(0,1,101)
# options
m.options.NODES = 6
m.options.SOLVER = 3
m.options.IMODE = 6
m.options.MAX_ITER = 500
m.options.MV_TYPE = 0
m.options.DIAGLEVEL = 0
# final time
tf = m.FV(value=1.0,lb=0.1,ub=100)
tf.STATUS = 1
# force
u = m.MV(value=0,lb=-1.1,ub=1.1)
u.STATUS = 1
u.DCOST = 1e-5
# variables
s = m.Var(value=0)
v = m.Var(value=0,lb=0,ub=1.7)
mass = m.Var(value=1,lb=0.2)
# differential equations scaled by tf
m.Equation(s.dt()==tf*v)
m.Equation(mass*v.dt()==tf*(u-0.2*v**2))
m.Equation(mass.dt()==tf*(-0.01*u**2))
# specify endpoint conditions
m.fix_final(s, 10.0)
m.fix_final(v, 0.0)
# minimize final time
m.Minimize(tf)
# Optimize launch
m.solve()
print('Optimal Solution (final time): ' + str(tf.value[0]))
# scaled time
ts = m.time * tf.value[0]
# plot results
plt.figure(1)
plt.subplot(4,1,1)
plt.plot(ts,s.value,'r-',linewidth=2)
plt.ylabel('Position')
plt.legend(['s (Position)'])
plt.subplot(4,1,2)
plt.plot(ts,v.value,'b-',linewidth=2)
plt.ylabel('Velocity')
plt.legend(['v (Velocity)'])
plt.subplot(4,1,3)
plt.plot(ts,mass.value,'k-',linewidth=2)
plt.ylabel('Mass')
plt.legend(['m (Mass)'])
plt.subplot(4,1,4)
plt.plot(ts,u.value,'g-',linewidth=2)
plt.ylabel('Force')
plt.legend(['u (Force)'])
plt.xlabel('Time')
plt.show()
There are a few problems that I fixed with your current solution:
Variables w and st are not used
The STATUS for p_s and s_s should be On (1) to be calculated by the solver
The number of time points (50000) is really long and will create a very large problem that will be hard to solve in one solution. You may consider breaking this into successive solutions that advance one cycle (m.options.TIME_SHIFT=1) or multiple (m.options.TIME_SHIFT=10) for each m.solve() command.
There may be references that can help with the problem formulation. It appears that you are taking a more physics-based approach than a data driven approach.
Switched to the APOPT solver for a successful solution.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=False)
#Constants
mass = m.Const(77) #mass of the rider
g = m.Const(9.81) #gravity
H = m.Const(1.2) #height of the rider
L = m.Const(value=1.4) #lenght of the wheelbase of the bicycle
E_n = m.Const(value=22000) #Energy that can be used
c_rr = m.Const(value=0.0035) #coefficient of drag
s_max = m.Const(value=0.52) #max steer angle
W_m = m.Const(value=1800) #max power that the rider can produce
vWn = m.Const(value=50) #maximal power output variation
vSn = m.Const(value=0.52) #maximal steer output variation
kv = m.Const(value=0.13) #air drag coefficient
ws = m.Const(value=0) #wind speed
Ix = m.Const(value=77) #inertia
W_c = m.Const(value=440) #critical power(watts)
Wj1 = m.Const(value=0.01) ##weighting factors that scale the penalisation
Wj2 = m.Const(value=0.01) #weighting factors that scale the penalisation
dist = 1000 ##distance that that the rider needs to travel
nt = 100 ##calculation at every 10 meters
m.time = np.linspace(0,dist,nt)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
slope = np.zeros(nt) #SET THE READ CURVATURE AND SLOPE TO 0 for experimentation later we will import it from real road.
curv = np.zeros(nt) #SET THE READ CURVATURE AND SLOPE TO 0 for experimentation later we will import it from real road.
####Import Road Characterisitc####
k = m.Param(value=curv) ##road_curvature
b = m.Param(value=slope) ##slope angle
###Control Variable###
p_s = m.MV(value=1,lb=-1000,ub=1000); p_s.STATUS = 1 ##power
s_s = m.MV(value=0,lb=-100,ub=100); s_s.STATUS = 1 ##steer
###State Variable###
# Not used
#w = m.Param(value=10,lb=-10000,ub=1800) #power done by the rider (positive:pedaling, negative:braking)
#st = m.Param(value=0,lb=-30,ub=30) ##steer angle
s = m.Var(value=1,lb=1e-4,ub=100) #speed along road
v = m.Var(value=1, lb=0, ub=16) #velocity
n = m.Var(value=0,lb=-4, ub=4) ##displacement fron the center of the road upper bound and lower bound are the road width
h = m.Var(value=0,lb=-10,ub=10) #heading of the bicycle
r = m.Var(0,lb=-0.78, ub=0.78) ##roll
r_dot = m.Var(value=0,lb=-100,ub=100) ##roll_rate
W_n = m.Var(value=0.1,lb=-1, ub=1) ##normalised power
s_n = m.Var(value=0,lb=-1, ub=1) #normalised steer angle
e = m.Var(value=22000, lb=0, ub=22000) #energy remaining
####Equations####
#1 dynamics of travelling speed s(s) along the reference line
m.Equation((1-(n-k))*s.dt()==v*m.cos(h))
#2:dynamics of the longitudinal velocity of the bicycle
c1 = m.Intermediate((v*mass)/W_m,'c1')
m.Equation(c1*s*v.dt()==(W_n
-( (v/W_m) * (mass*g* (c_rr* m.cos(b)+m.sin(b))) )
-((v/W_m) * kv*(v-(ws*h))**2)
)
)
#3: dynamic of the lateral displacement
m.Equation(s*n.dt()==m.sin(k))
#4: heading of the bicycle ๐ผ(s):
m.Equation((L*s)*h.dt()==(s_n*s_max)-k*(L*s))
#5&6: dynamics of the roll angle ๐ (rad) and its rate of change ๐dot(s)
m.Equation(s*r.dt()==(r_dot))
m.Equation(((h**2)*mass+Ix)*(g*L*s)*r_dot.dt()==(H*mass*g)*((v**2)*s_max*s_n+L*g*r))
#7: dynamics of the normalised power output Wn
m.Equation(s*W_n.dt()==p_s)
##8: dynamics of the normalised steering angle ๐ฟn
m.Equation(s*s_n.dt()==s_s)
#9: dynamic equation describing the evolution of the anaerobic sources
# use lower bound on W_n instead of m.min2(0,W_n)
m.Equation((s*E_n)*e.dt()==(-(W_n*W_m-W_c) ))
####OBJECTIVE####
m.Minimize(m.integral( (1/s) * (1+(Wj1*((p_s/vWn)**2))+(Wj2*((s_s/vSn)**2))) )*final)
m.options.IMODE = 6 # optimal control
m.options.SOLVER = 1 # solver (APOPT)
m.options.DIAGLEVEL=0
#m.open_folder()
m.solve(disp=True, debug=True) # Solve
With this script, I get a successful solution but I haven't investigated the objective function to see if it is giving a reasonable answer.
----------------------------------------------------------------
APMonitor, Version 0.9.2
APMonitor Optimization Suite
----------------------------------------------------------------
--------- APM Model Size ------------
Each time step contains
Objects : 0
Constants : 16
Variables : 15
Intermediates: 1
Connections : 0
Equations : 12
Residuals : 11
Number of state variables: 2970
Number of total equations: - 2772
Number of slack variables: - 0
---------------------------------------
Degrees of freedom : 198
----------------------------------------------
Dynamic Control with APOPT Solver
----------------------------------------------
Iter Objective Convergence
0 2.51001E+03 1.00000E+00
1 4.36075E+04 5.66676E-01
2 3.43092E+03 5.36156E-01
3 7.36773E+03 4.16203E-01
4 2.75250E+03 9.29407E-02
5 4.12278E+03 1.93521E-02
6 5.80466E+05 7.35244E-02
7 4.99119E+04 1.27246E-01
8 2.11556E+03 4.52552E-01
9 6.32932E+03 2.14605E-01
Iter Objective Convergence
10 8.16639E+01 2.76062E-01
11 6.80002E+02 8.83214E-01
12 4.71706E+01 2.87555E-01
13 1.28152E+02 1.36994E-03
14 1.01698E+01 1.08406E+01
15 1.13082E+01 3.00869E+00
16 1.03199E+01 8.67971E+00
17 1.02638E+01 1.28697E-02
18 1.02636E+01 5.64896E-05
19 1.02636E+01 6.72710E-07
Iter Objective Convergence
20 1.02636E+01 6.72710E-07
Successful solution
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 3.1271 sec
Objective : 10.263550885927089
Successful solution
---------------------------------------------------
You may want to create plots to make sure that the equations and solver are giving a correct solution. Here is an animation and source code that shows how to set up a model predictive controller with a finite horizon and that advances in time (or space) for each solve command.
The finite horizon approach is used commonly in industrial control to ensure that the optimizer can finish within the required cycle time and balance that with the length of the horizon to "see" future constraints and opportunities for energy or production optimization.
Related
PSA: I am very new to gekko, thus I might be missing something very obvious here.
I have been trying to find the solution to an optimal control problem, namely trajectory optimization of a regular vehicle, under certain speed constraints at certain distances along their trip. In order to do this, I tried using a pwl function based on the distance and speed constraint data and using v_max as a constraint to v. As a objective function, I use a Vehicle Specific Power (VSP) approximation.
The computation keeps going until the maximum no. of iterations is reached and cancels. Is there maybe a way to discretize the search space of this problem to make it solvable in acceptable time trading off computation time for accuracy?
goal_dist = The distance that needs to be covered
max_accel = maximum possible acceleration of the vehicle
max_decel = maximum possible deceleration of the vehicle
max_velocity = maximum possible velocity of the vehicle
min_velocity = minimum possible velocity of the vehicle
trip_time = No. of discrete data points (1s apart)
distances = array of length trip_time of discrete distance values based on GPS data points of the desired trip
speed_limits = array of length trip_time of discrete speed limits based on GPS data points of the desired trip
slope = array of length trip_time of discrete angle values
def optimal_trip(goal_dist, max_accel, max_decel, max_velocity, min_velocity, trip_time, distances ,speed_limits, slope):
model = GEKKO(remote=True)
model.time = [i for i in range(trip_time)]
x = model.Var(value=0.0)
v = model.Var(value=0.0, lb = min_velocity, ub = max_velocity)
v_max = model.Var()
slope_var = model.Var()
a = model.MV(value=0, lb=max_decel ,ub=max_accel)
a.STATUS = 1
#define vehicle movement
model.Equation(x.dt()==v)
model.Equation(v.dt()==a)
# path constraint
model.Equation(x >= 0)
#aggregated velocity constraint
model.pwl(x, v_max, distances, speed_limits)
model.Equation(v_max>=v)
#slope is modeled as a piecewise linear function
model.pwl(x, slope_var, distances, slope)
#End state constraints
model.fix(x, pos=trip_time-1,val=goal_dist) # vehicle must arrive at destination
model.fix(v, pos=trip_time-1,val=0) # vehicle must be fully stopped
#VSPI Objective function
obj = (v * (1.1 * a + 9.81 * slope_var + 0.132) +0.0003002*pow(v, 3))
model.Obj(obj)
# solve
model.options.IMODE = 6
model.options.REDUCE = 3
model.solve(disp=True)
return x.value, v.value, obj.value
Could someone shed some light onto this?
Here is a version of the model with sample values that solves successfully:
from gekko import GEKKO
import numpy as np
min_velocity = 0
max_velocity = 10
max_decel = -1
max_accel = 1
distances = np.linspace(0,20,21)
goal_dist = 200
trip_time = 100
# set up PWL functions
distances = np.linspace(0,1000,10)
speed_limits = np.ones(10)*5
speed_limits[5:]=7
slope = np.zeros(10)
slope[3:5]=1; slope[7:9]=-1
model = GEKKO(remote=True)
model.time = [i for i in range(trip_time)]
x = model.Var(value=0.0)
v = model.Var(value=0.0, lb = min_velocity, ub = max_velocity)
v_max = model.Var()
slope_var = model.Var()
a = model.MV(value=0, lb=max_decel ,ub=max_accel)
a.STATUS = 1
#define vehicle movement
model.Equation(x.dt()==v)
model.Equation(v.dt()==a)
# path constraint
model.Equation(x >= 0)
#aggregated velocity constraint
model.pwl(x, v_max, distances, speed_limits)
model.Equation(v_max>=v)
#slope is modeled as a piecewise linear function
model.pwl(x, slope_var, distances, slope)
#End state constraints
model.fix(x, pos=trip_time-1,val=goal_dist) # vehicle must arrive at destination
model.fix(v, pos=trip_time-1,val=0) # vehicle must be fully stopped
#VSPI Objective function
obj = (v * (1.1 * a + 9.81 * slope_var + 0.132) +0.0003002*pow(v, 3))
model.Obj(obj)
# solve
model.options.IMODE = 6
model.options.REDUCE = 3
model.solve(disp=True)
It may be that the values you are using cause an infeasible solution. Here are some suggestions to help the model solve more reliably:
Use upper bounds on variables instead of general inequality constraints when possible.
# remove these lines
#model.Equation(x >= 0)
#x = model.Var(value=0.0)
# put lower bound on x
x = model.Var(value=0,lb=0)
Use soft terminal constraints instead of hard terminal constraints.
#End state constraints
# vehicle must arrive at destination
#model.fix(x, pos=trip_time-1,val=goal_dist)
# vehicle must be fully stopped
#model.fix(v, pos=trip_time-1,val=0)
p = np.zeros_like(model.time); p[-1]=1
final = model.Param(p)
model.Minimize(1e4*final*(v**2))
model.Minimize(1e4*final*((x-goal_dist)**2))
Increase maximum iterations. Sometimes the solver needs more iterations to find a solution.
model.options.MAX_ITER=1000
The final version of the model has these changes. I may help converge to a solution and avoid maximum iterations or an infeasible solution.
from gekko import GEKKO
import numpy as np
min_velocity = 0
max_velocity = 10
max_decel = -1
max_accel = 1
distances = np.linspace(0,20,21)
goal_dist = 200
trip_time = 100
# set up PWL functions
distances = np.linspace(0,1000,10)
speed_limits = np.ones(10)*5
speed_limits[5:]=7
slope = np.zeros(10)
slope[3:5]=1; slope[7:9]=-1
model = GEKKO(remote=True)
model.time = [i for i in range(trip_time)]
x = model.Var(value=0.0, lb=0)
v = model.Var(value=0.0, lb = min_velocity, ub = max_velocity)
v_max = model.Var()
slope_var = model.Var()
a = model.MV(value=0, lb=max_decel ,ub=max_accel)
a.STATUS = 1
#define vehicle movement
model.Equation(x.dt()==v)
model.Equation(v.dt()==a)
#aggregated velocity constraint
model.pwl(x, v_max, distances, speed_limits)
model.Equation(v_max>=v)
#slope is modeled as a piecewise linear function
model.pwl(x, slope_var, distances, slope)
#End state constraints
# vehicle must arrive at destination
#model.fix(x, pos=trip_time-1,val=goal_dist)
# vehicle must be fully stopped
#model.fix(v, pos=trip_time-1,val=0)
p = np.zeros_like(model.time); p[-1]=1
final = model.Param(p)
model.Minimize(1e4*final*(v**2))
model.Minimize(1e4*final*((x-goal_dist)**2))
#VSPI Objective function
obj = (v * (1.1 * a + 9.81 * slope_var + 0.132) +0.0003002*pow(v, 3))
model.Minimize(obj)
# solve
model.options.IMODE = 6
model.options.REDUCE = 3
model.options.MAX_ITER=1000
model.solve(disp=True)
If you ask another question on StackOverflow, don't forget to include a minimal and complete working example that replicates the problem.
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()
So I am simulating plane flight. The plane flies certain distance (pathx and pathy variables) and then the simulation stops, when certain pathx and pathy values are reached. The solver is trying to minimize the fuel consumption (m.Maximize(mass*tf*final), by maximizing the mass value. The solver controls the accelerator pedal position (Tcontr).
Right now the termination condition is defined like this:
For X axis:
m.Equation(x*final<=pathx)
and
m.Minimize(final*(x-pathx)**2)
And for Y axis:
m.Equation(y*final<=pathy)
and
m.Minimize(final*(y-pathy)**2)
And right now the simulation ends when the desired X value is achieved, while desired Y values is not being achieved.
How do I force the simulation to end when both desired (X and Y) values are achieved?
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.Const(value=0.1)#drag coef
Cl = m.Var(value=1)#lift couef
FuelFlow = m.Var()
D = m.Var(value=25000,lb=0)#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()#throttle
V = m.Var(value=100,lb=50,ub=240)#velocity
gamma = m.Var(value=0)# Flight-path angle
gammaa = gamma.value
Xi = m.Var(value=0)# Heading angle
Xii = Xi.value
x = m.Var(value=0,lb=0)#x position
y = m.Var(value=0,lb=0)#y position
h = m.Var(value=1000,lb=0)# height
mass = m.Var(value=60000,lb=10000)
pathx = m.Const(value=50000) #intended distance length
pathy = m.Const(value=50000)
L = m.Var(value=0.1)#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.1,ub=1)# solver controls throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
#Mu = m.Var(value=0)
Mu = m.MV(value=0,lb=-1.5,ub=1.5)# solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 0
Muu = Mu.value
# 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))#
m.Equation(x.dt()==tf*(V*(math.cos(gammaa.value))*(math.cos(Xii.value))))#
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)
m.Equation(Xi.dt()==tf*((L*math.sin(Muu.value))/(mass*V)))
m.Equation(y.dt()==tf*(V*(math.cos(gammaa.value))*(math.sin(Xii.value))))#
m.Equation(y*final<=pathy)
# Objective Function
m.Minimize(final*(x-pathx)**2) #1D part
m.Minimize(final*(y-pathy)**2) #2D 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(8)
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,Mu.value,'p-',LineWidth=2,label=r'$Mu$')
axs[6].legend(loc='best')
axs[7].plot(tm,y.value,'p-',LineWidth=2,label=r'$y$')
axs[7].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
Important Update
It looks like termination conditions (all of the above) work as intended. Y axis values can not be reached, because it looks like the math involving it is not working properly.
Y value is being calculated by this equation:
I have turned into this: m.Equation(y.dt()==tf*(V*(math.cos(gammaa.value))*(math.sin(Xii.value))))
Where: V is true air speed (works properly);
tf is a variable that controls simulation time (works properly);
gammaa is derived from gamma, which is flight-path angle, which is 0 in this simulation (works properly);
Xii is derived from Xi, which is heading angle (the main culprit).
Xi value is defined by this equation:
I turned it into this: m.Equation(Xi.dt()==tf*((L*math.sin(Muu.value))/(mass*V)))
Where: L is lift force (works properly);
mass is mass (works properly);
V is true air speed (works properly);
Muu is derived from Mu, which is bank angle, the solver controlled variable (probably the bad apple here).
Y value calculation should be working like this: solver changes Mu, which affects The heading angle Xi, which then should affect Y value.
After some experiments it looks like the range of changes of the Mu value during the simulation is so small, that it barely affects Y value. But it should be a lot wider since Mu boundaries are quite wide (lb=-1.5,ub=1.5). I tried to remove those boundaries, but it did not affect the results of the simulation.
What could be messing up everything here?
Try adding a hard terminal constraint for both:
m.Equation((x-pathx)*final==0)
m.Equation((y-pathy)*final==0)
Alternatively, increase the weight on the terminal condition.
w = 1e3
m.Minimize(w*final*(x-pathx)**2) #1D part
m.Minimize(w*final*(y-pathy)**2) #2D part
You may need to use one or both strategies. Terminal conditions can be challenging to solve.
Response to Important Update
Use the Gekko functions with m.sin() and m.cos() instead of the math functions. Also, don't use .value in the equations. Gekko needs the full symbolic graph so that it can compile the equations into byte-code and produce function evaluations and the derivatives with automatic differentiation.
m.Equation(y.dt()==tf*(V*(m.cos(gammaa))*(m.sin(Xii))))
It also helps to avoid divide by zero by multiplying any denominator over to the other side.
m.Equation(mass*V*Xi.dt()==tf*((L*m.sin(Muu))))
im quite new in python and i've been trying to solve a system of 2 simultaneous differential equations with 2 unkowns. Both variables T,X are a function of time, the derivative of T: dT/dt depends on both T and X while dX_dt depends only on X. My first thought was to create a function fun(t,T,X) which returns the values of dT/dt and dX/dt and use solve_ivp but it didn't work well. Can someone please tell me what is the proper way of solving such a system of ODEs? Thanks in advance. This is the script:
# Imports
import math
import numpy as np
from scipy.integrate import solve_ivp
# PARTICLE
R =50*10**(-6) # Particle Radius (m)
rho = 575.4 # Initial Particle density (kg/m^3)
mp0 = rho*4*math.pi*R**3/3 # Particle's initial mass
#t_c = 0.60 # C mass fraction in coal
t_ash = 0.245 #initial ash mass fraction in coal
mc0=mp0*(1-t_ash) # initial carbon mass fraction in coal
SSA =100000 # Coal Specific Surface Area (kg/m^2)
ep = 0.85 # Particle's emissivity
E= 153299 # Carbon combustion activation energy (J/mole)
# WALL AND AIR TEMPERATURES and pressure
Tw = 1000 # Wall temperature (K)
Ta = 300 # Air temperature (K)
p = 101325 # Aur pressure (Pa)
# OTHER CONSTANTS
sb = 5.670374419*10**(-8) # Stephan-Boltzman Constant ()
Rg = 8.31446261815324 # Gas constant (J/K mol)
Mc = 12.0107*10**(-3) # Carbon Molar Mass (kg/mole)
X_O2 = 0.20947 # Oxygen's Volumetric fraction in Air
Co2inf = X_O2*p/(Rg*Ta) # # Oxygen molar concentration in air (moles/m^3)
def fun(t,T, X):
# Cp of carbon (J/kg K)
Cpc = 3*Rg/Mc*math.exp(1200/T)*((math.exp(1200/T)-1)/(1200/T))**2
# Cp of ash (J/kg K)
Cpash = 754+0.586*(T-273.15)
# Reaction Enthalpy of CO (J/mole -absolute value - temperature correction)
DHco = 110.5*10**3 + 6.38*(T-298.15)
# Reaction Enthalpy of CO2 (J/mole -absolute value - temperature correction)
DHco2 = 393.5*10**3 + 13.1*(T-298.15)
# CO/CO2 ratio
CO_CO2 = 10**2.5*math.exp(-25000/(Rg*T))
# Particle-Air boundary layer mean temperature
Tm = (T+Ta)/2
# Air thermal conductivity (W/m K -was used for gas with different O2 content!!!!)
lamda = 0.0207*(Tm/273.15)**2*(1+113.15/273.15)/(1+113.15/Tm)
# Convection coefficient (W/m^2 K - Nusselt=2)
h = lamda/R
# Effective diffusivity of oxygen within the particle, (m^2/s)
Do2 = 1.78*10**(-5)*(Tm/273.15)**1.75
# Mass transfer coefficient of oxygen (m/s - Sherwood=2)
kd = Do2/R
# Kinetic constant (m/s)
ks = 1050*math.exp(-153200/(Rg*T))
# L parameter
L = (CO_CO2+1)/(CO_CO2/2+1)
# Oxygen Flux
FluxO2 = Co2inf/(3*L/(ks*rho*4*math.pi*R**3*SSA)+1/(kd*4*math.pi*R**2))
# HEAT BALANCE EQUATION
dT_dt= (4*math.pi*R**2*ep*sb*(Tw**4-T**4) + 4*math.pi*R**2*h*(Ta-T) + (CO_CO2*DHco+DHco2)*FluxO2/(1+CO_CO2/2))/(mc0*(1-X)*Cpc+mp0*t_ash*Cpash)
# Combustion Reaction
dX_dt= Co2inf*Mc*ks*SSA*(1-X)
return [dT_dt, dX_dt]
Init_cond=[300,0]
OutputTimes = np.linspace(0, 20, 100)
ans = solve_ivp(fun, (0, 20), Init_cond, method='RK45', t_eval=OutputTimes)
The same way that your return value, the derivative of the state, is a tuple (or conceptually a vector), the ODE function also has to accept the state as a tuple/vector of the same dimension. The solver is based on implementing systems of ODE via vector-valued vector functions.
def fun(t,u):
T, X = u;
# etc.
With this change your program should run or at least give more interesting errors.
You might want to try out if the method="Radau" results in a faster integration with much less internal steps, as it is often the case.
Is there a way to constrain the objective function to be within a range in Python Gekko? I am working through the example optimization problem on the economics of a commercial fishery over a 10 year operation. The adjustable parameter is the production rate (harvest rate) of the fish. The objective function is the profit from the operation over the 10 year period. The optimization problem in mathematical terms is:
The solution and Python Gekko code are:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
# create GEKKO model
m = GEKKO()
# time points
n=501
m.time = np.linspace(0,10,n)
# constants
E = 1
c = 17.5
r = 0.71
k = 80.5
U_max = 20
# fishing rate
u = m.MV(value=1,lb=0,ub=1)
u.STATUS = 1
u.DCOST = 0
# fish population
x = m.Var(value=70)
# fish population balance
m.Equation(x.dt() == r*x*(1-x/k)-u*U_max)
# objective (profit)
J = m.Var(value=0)
# final objective
Jf = m.FV()
Jf.STATUS = 1
m.Connection(Jf,J,pos2='end')
m.Equation(J.dt() == (E-c/x)*u*U_max)
# maximize profit
m.Maximize(Jf)
# options
m.options.IMODE = 6 # optimal control
m.options.NODES = 3 # collocation nodes
m.options.SOLVER = 3 # solver (IPOPT)
# solve optimization problem
m.solve()
# print profit
print('Optimal Profit: ' + str(Jf.value[0]))
# plot results
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,J.value,'r--',label='profit')
plt.plot(m.time[-1],Jf.value[0],'ro',markersize=10,\
label='final profit = '+str(Jf.value[0]))
plt.plot(m.time,x.value,'b-',label='fish population')
plt.ylabel('Value')
plt.legend()
plt.subplot(2,1,2)
plt.plot(m.time,u.value,'k.-',label='fishing rate')
plt.ylabel('Rate')
plt.xlabel('Time (yr)')
plt.legend()
plt.show()
One of the observations I've had in chemical manufacturing is that optimization sometimes leads to non-intuitive solutions because the optimizer (IPOPT) is going to take the process to the absolute limit to achieve even a few dollars more of profitability. Is there a way to constrain the objective function (profit in this case) so that the business stays viable but the optimizer doesn't give a solution that is pushing the limits of the equipment (or the fish population in this case).
You can set a limit on the objective by adding an upper bound such as:
Jf = m.FV(ub=100) # final objective
This sets an upper bound ub of 100. Even though you can do this, there are a variety of reasons why you may not want to put a constraint on the objective. One is that this could lead to an infeasible solution if the optimizer cannot achieve that limit. Another is that you may still not achieve an "intuitive solution" or one that wouldn't push your equipment (such as fishing boats or your chemical manufacturing plant) to the limit in certain periods. As Tim mentioned in the comment, a better way is to put constraints on things that you can control such as fishing rate. A few parameters that you may find useful are the move suppression factor DCOST, maximum movement DMAX, and cost for fishing rate COST. Maybe it isn't reasonable to ramp up to full fishing rate in one year and then drop to 40% for 5+ years. There may also be costs associated with building up the fishing fleet that would be not be reflected in your current solution.
You can also set up the objective as a soft constraint between certain target values and then let other objectives such as maximize fish population be the driving force for decisions between the upper and lower profitability targets.
# objective (profit)
J = m.CV(value=0)
J.SPHI = 100; J.WSPHI = 1000
J.SPLO = 80; J.WSPLO = 1000
m.options.CV_WGT_START = n-1 # start at n-1
J.STATUS = 1
m.Maximize(x) # maximize fish population
This gives a solution between profit limits of 80 to 100 but also maximizes the fish population.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
# create GEKKO model
m = GEKKO()
# time points
n=501
m.time = np.linspace(0,10,n)
# constants
E = 1
c = 17.5
r = 0.71
k = 80.5
U_max = 20
# fishing rate
u = m.MV(value=1,lb=0,ub=1)
u.STATUS = 1
u.DCOST = 0
# fish population
x = m.Var(value=70)
# fish population balance
m.Equation(x.dt() == r*x*(1-x/k)-u*U_max)
# objective (profit)
J = m.CV(value=0)
J.SPHI = 100; J.WSPHI = 1000
J.SPLO = 80; J.WSPLO = 1000
m.options.CV_WGT_START = n-1 # start at n-1
J.STATUS = 1
m.Maximize(x) # maximize fish population
m.Equation(J.dt() == (E-c/x)*u*U_max)
# options
m.options.IMODE = 6 # optimal control
m.options.NODES = 3 # collocation nodes
m.options.SOLVER = 3 # solver (IPOPT)
# solve optimization problem
m.solve()
# print profit
print('Optimal Profit: ' + str(J.value[-1]))
# plot results
plt.figure(1)
plt.subplot(2,1,1)
plt.plot([0,10],[100,100],'k:',label='target profit range')
plt.plot([0,10],[80,80],'k:')
plt.plot(m.time,J.value,'r--',label='profit')
plt.plot(m.time[-1],J.value[-1],'ro',markersize=10,\
label='final profit = '+str(J.value[-1]))
plt.plot(m.time,x.value,'b-',label='fish population')
plt.ylabel('Value')
plt.legend()
plt.subplot(2,1,2)
plt.plot(m.time,u.value,'k.-',label='fishing rate')
plt.ylabel('Rate')
plt.xlabel('Time (yr)')
plt.legend()
plt.show()