You need to reform the final equation for position. Otherwise, it is 0==12000
for all but the final point. The original form gives an infeasible solution.
m.Equation(xpos*final == x_f)
Here is the correct form of that equation.
m.Equation(final*(xpos-x_f)==0)
In this form, it is 0==0
everywhere except at the end where it is 1*(xpos-12000)==0
. It also helps to give an objective function term to guide the final condition with:
m.Minimize(final*(xpos-x_f)**2)
This guides the solver towards the feasible solution to meet the end point.
The inequality constraint
m.Equation(E_an*final >= 0)
isn't needed if E_an
has a lower bound of zero with E_an = m.Var(lb=0)
or by setting E_an.LOWER=0
. It should always be positive throughout the entire time horizon, not just at the final solution. It is easier for the solver to have a constraint on the variable (e.g. lower bound) than to add an additional inequality constraint (e.g. with m.Equation()
).
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO() # initialize gekko
nt = 101
m.time = np.linspace(0,1,nt)
#Parameters, taken from paper, idepdendant of the path
#mass = 80
g = 9.81
mu = 0.004
r_w = 0.335
I_s = 0.658
M = 80 #mass + (I_s/r_w**2)
C_p = 0.7
rho = 1.2
A = 0.4
eta = 0.95
P_c = 150
P_max = 1000
#Path dependant
x_f = 12000
s = 0.081
#Define anaerobic energy reserve, dependant on the cyclist
E_an_init = 20000
#Variables
xpos = m.Var(value = 0, lb = 0,ub = x_f,name='xpos')
E_kin = m.Var(value = 0, lb=1e-3, name='E_kin')#start at 1m/s
E_an = m.Var(value = E_an_init, lb=0 , ub=E_an_init, name='E_an')
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# optimize final time
tf_guess = 900
tf = m.FV(value=tf_guess,lb=300, ub=10000, name='tf')
tf.STATUS = 1
# control changes every time period
Pow = m.MV(value=100,lb=0,ub=P_max, name='Pow')
Pow.STATUS = 1
# Equations
Esr = m.Intermediate((2*E_kin/M)**0.5)
m.Equation(xpos.dt()==Esr*tf)
m.Equation(E_kin.dt()==(eta*Pow-Esr*(M*g*(s+mu*m.cos(m.atan(s)))
+ C_p*rho*A*E_kin/M))*tf)
m.Equation(E_an.dt()==(P_c-Pow)*tf)
m.Minimize(final*(xpos-x_f)**2)
m.Equation(final*(xpos-x_f)==0)
m.Minimize(final*(tf**2))
m.options.IMODE = 6 # optimal control mode
m.solve(disp=True) # solve
print('Final Time: ' + str(tf.value[0]) + "s")
print('Final Position: ' + str(xpos.value[-1]))
print('Final Energy: ' + str(E_an.value[-1]))
t = m.time*tf.value[0]/60.0
plt.figure(figsize=(9,6))
plt.subplot(4,1,1)
plt.plot(t,xpos.value,'r-',label='xpos')
plt.legend()
plt.subplot(4,1,2)
plt.plot(t,E_kin.value,'b-',label='E_kin')
plt.legend()
plt.subplot(4,1,3)
plt.plot(t,E_an.value,'k-',label='E_an')
plt.legend()
plt.subplot(4,1,4)
plt.plot(t,Pow.value,'-',color='orange',label='Pow')
plt.legend()
plt.xlabel('Time (min)')
plt.show()
The solution doesn't look quite right for a competitive cyclist to cover 12 km. It should be much faster so there may be a problem with the current equations. The final position constraint is met and the final energy is zero but the time should be faster.
Final Time: 5550.6961268s
Final Position: 12000.0
Final Energy: 0.0