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()
Related
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
Good afernoon community, recently someone recommends me GEKKO library to do dynamic optimization through python. I'm trying to replicate this paper with that. Basically the model is:
According to the paper E is control variable and K is a state variable, the rest are constant values. I think my error is when I define E as a control variable, because when I do the output is that GEKKO can't find a solution. That's is the reason that belows I define as a parameter.
# Initialize GEKKO
m = GEKKO()
# Define values:
# Constant values
beta_1, beta_2, gamma, delta, s, alpha, L_bar = 0.2, 0.8, 0.5, 0.05, 0.4719, 10, 900000000
# Time period
n = 100
t = np.linspace(0, 15, n)
m.time = t
# Set initial conditions:
# Define K(0) = K_0 initial capital
K = m.Var(value = 0)
# Restrictions problem:
# Define constraints
import math
E = m.Param(-4 * (np.exp(t/2) - np.exp(-1))) # Control variable E
Y = m.Param((1 - gamma) * (K**beta_1) * (L_bar**(beta_2)) + gamma * E)
K_dot = m.SV(1, lb = 0, ub = 1)
m.Equation(K_dot.dt() == s * (Y - E) - delta * K)
J = m.Var(value = 0)
Jf = m.FV()
Jf.STATUS = 1
# Define objective function :
m.Connection(Jf,J,pos2='end')
m.Equation(J.dt() == -1 *(E**2) - alpha * K_dot)
# Set solving options
m.Obj(-Jf) # maximize profit
# Set solving options
m.options.IMODE = 6 # optimal control
m.options.NODES = 3 # collocation nodes
m.options.SOLVER = 3 # solver (IPOPT)
# Solve maximization problem
m.solve(disp = False) # Solve
plt.plot(t, K, "--r")
Taking a look to the documentation, I think my problem is a mix between the "load following" exmple and the "optimal control with economic objective". In order to know if I did in the right way, I use figure 5 and 6 as reference. For the figure 5 when x = 11 there is change in the graphic...but for me it isn't. While in figure 6, the y axis is from [0, -3k] and according to my script is from [0, -7k].
The decision variable E should be an MV() type and the Y variable should be an Intermediate type.
from gekko import GEKKO
import numpy as np
# Initialize GEKKO
m = GEKKO()
# Define values:
# Constant values
beta_1, beta_2 = 0.2, 0.8
gamma, delta, s, alpha, L_bar = 0.5, 0.05, 0.4719, 10, 900000000
# Time period
n = 100
t = np.linspace(0, 15, n)
m.time = t
# Set initial conditions:
# Define K(0) = K_0 initial capital
K_0 = 0.1
K = m.Var(K_0)
# Restrictions problem:
# Define constraints
E = m.MV(lb=0.1,ub=0.2); E.STATUS=1 # Variable E is adjusted to Maximize objective
Y = m.Intermediate((1-gamma) * (K**beta_1) * (L_bar**(beta_2)) + gamma*E)
m.Equation(K.dt() == s * (Y-E) - delta*K)
p = np.zeros(n); p[-1]=1; final=m.Param(p)
m.Maximize(final*m.integral(-1 *(E**2) - alpha*K)) # maximize profit
# Set solving options
m.options.IMODE = 6 # optimal control
m.options.NODES = 3 # collocation nodes
m.options.SOLVER = 1 # solver (IPOPT)
m.options.MAX_ITER=1000
# Solve maximization problem
m.solve(disp = True) # Solve
plt.plot(t, K, "--r")
plt.show()
You will need to find the correct value of K_0 from the paper and fill in any additional information such as the correct bounds on E. The solution currently appears unbounded (profit as a negative of the minimization increases with each solver iteration, up to 1000).
995 -1.11943E+06 9.76562E-04
996 -1.12056E+06 9.76562E-04
997 -1.12168E+06 9.76562E-04
998 -1.12281E+06 9.76562E-04
999 -1.12393E+06 9.76562E-04
Iter Objective Convergence
1000 -1.12506E+06 9.76562E-04
Maximum iterations
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 11.9291999999987 sec
Objective : -1125059.40772567
Unsuccessful with error code 0
---------------------------------------------------
There are a few additional dynamic optimization problem examples that can help:
Basic Examples
More Advanced Examples
Energy Systems
The commercial fishery example may be the closest application to the one you are solving.
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.
I am trying to find a trajectory that minimizes the squared integral of the force to move a block from one point to another. Here are the system dynamics:
dx/dt = v (derivative of position is velocity)
dv/dt = u (derivative of velocity is acceleration, which is what I am trying to minimize)
min integral of u**2
The initial conditions and final conditions are:
x(0) = 0, v(0) = 0
x(1) = 1, v(1) = 1
I have implemented this in python using the Gekko library, but I cannot get the final conditions working properly. Using m.fix() to fix the end position makes the problem unsolvable.
Reading online, I used m.Minimize() to make a soft constraint, but the solution was very far off from the end conditions. I added an extra equation to make the velocity less than zero at the end, and that made the solution look like the correct solution, albeit the end position was wrong (if the solution was scaled by a factor, it would be correct).
I should I properly solve this problem?
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
##model
m = GEKKO() # initialize gekko
nt = 101
m.time = np.linspace(0,1,nt)
# Variables
x = m.Var(value=0)
v = m.Var(value=0)
u = m.Var( fixed_initial=False)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==v)
m.Equation(v.dt()==u)
#m.Equation(x*final >= 1) ##error: Solution Not Found
m.Equation(v*final <= 0)
m.Minimize(final*(x-1)**2)
m.Minimize(final*(v-0)**2)
m.Obj(m.integral(u**2)*final) # Objective function
m.options.IMODE = 6 # optimal control mode
##solve
m.solve() # solve
##plot
plt.figure(1) # plot results
plt.plot(m.time,x.value,'k-',label=r'$x$')
plt.plot(m.time,v.value,'b-',label=r'$v$')
plt.plot(m.time,u.value,'r--',label=r'$u$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
##show plot
plt.show()
Plot of my results
You can fix the problem by putting a higher weight on the final conditions:
m.Minimize(final*1e5*(x-1)**2)
m.Minimize(final*1e5*(v-0)**2)
There is still some tradeoff with the u minimization but it is minimal.
The constraint m.Equation(x*final >= 1) is infeasible when final=0 because this results in the inequality 0 >= 1. If you'd like to use the final position constraint, you'll need to use m.Equation((x-1)*final >= 0) so that the constraint is enforced only at the end but is feasible (0 >= 0) elsewhere. You don't necessarily need the hard constraints with the soft (objective function) constraints for the final condition. Here is a related problem with an inverted pendulum.
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)
# Variables
x = m.Var(value=0)
v = m.Var(value=0)
u = m.Var(fixed_initial=False)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==v)
m.Equation(v.dt()==u)
m.Equation((x-1)*final >= 0)
m.Equation(v*final <= 0)
m.Minimize(final*1e5*(x-1)**2)
m.Minimize(final*1e5*(v-0)**2)
m.Obj(m.integral(u**2)*final) # Objective function
m.options.IMODE = 6 # optimal control mode
m.solve() # solve
plt.figure(1) # plot results
plt.grid()
plt.plot(m.time,x.value,'k-',label=r'$x$')
plt.plot(m.time,v.value,'b-',label=r'$v$')
plt.plot(m.time,u.value,'r--',label=r'$u$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
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()