2

I have been using gekko to optimize a bioreactor using example 12 (https://apmonitor.com/wiki/index.php/Main/GekkoPythonOptimization) as a basis.

My model is slightly more complicated with 6 states, 7 states and 2 manipulated variables. When I run it for small values of time (t ~20), the simulation is able to converge (albeit requiring a fine time resolution (dt < 0.1). However, when I try to extend the time (e.g., t = 30), it fails quite consistently with the following error:

EXIT: Converged to a point of local infeasibility. Problem may be infeasible

I have tried the following:

  1. Employing different solvers with m.options.SOLVER = 1,2,3
  2. Increasing m.options.MAX_ITER to 10000
  3. Decreasing m.options.NODES to 1 (a lower order descretization seems to help with convergence)
  4. Supplying a reasonable initial guess to the MVs by specifying a value in the declaration: D = m.MV(value=0.1,lb=0.0,ub=0.1). From some of the various posts, it seems this should help.

I am not too sure how to go about solving this. For a simplified model (3 states, 5 parameters and 2 MVs), gekko is able to optimize it quite well (though it fails somewhat when I try to go to large t) even though the rate constants of the simplified model are a subset of the full model.

My code is as follows:

from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt

#Parameters and IC
full_params = [0.027,2.12e-9,7.13e-3,168,168,0.035,1e-3]
full_x0 = [5e6,0.0,0.0,0.0,1.25e5,0.0]
mu,k1,k2,k3,k33,k4, f= full_params

#Initialize model
m = GEKKO()

#Time discretization
n_steps = 201
m.time = np.linspace(0,20,n_steps)

#Define MVs
D = m.MV(value=0.1,lb=0.0,ub=0.1)
D.STATUS = 1
D.DCOST = 0.0
Tin = m.MV(value=1e7,lb=0.0,ub=1e7)
Tin.STATUS = 1
Tin.DCOST = 0.0

#Define States
T = m.Var(value=full_x0[0])
Id = m.Var(value=full_x0[1])
Is = m.Var(value=full_x0[2])
Ic = m.Var(value=full_x0[3])
Vs = m.Var(value=full_x0[4])
Vd = m.Var(value=full_x0[5])

#Define equations
m.Equation(T.dt() == mu*T -k1*(Vs+Vd)*T + D*(Tin-T))
m.Equation(Id.dt() == k1*Vd*T -(k1*Vs -mu)*Id -D*Id)
m.Equation(Is.dt() == k1*Vs*T -(k1*Vd + k2)*Is -D*Is)
m.Equation(Ic.dt() == k1*(Vs*Id + Vd*Is) -k2*Ic -D*Ic)
m.Equation(Vs.dt() == k3*Is - (k1*(T+Id+Is+Ic) + k4 + D)*Vs)
m.Equation(Vd.dt() == k33*Ic + f*k3*Is - (k1*(T+Id+Is+Ic) + k4 + D)*Vd)

#Define objective function
J = m.Var(value=0) # objective (profit)
Jf = m.FV() # final objective
Jf.STATUS = 1
m.Connection(Jf,J,pos2="end")
m.Equation(J.dt() == D*(Vs + Vd))
m.Obj(-Jf)
m.options.IMODE = 6  # optimal control
m.options.NODES = 1  # collocation nodes
m.options.SOLVER = 3
m.options.MAX_ITER = 10000

#Solve
m.solve()

For clarity, the model equations are:

enter image description here

I would be grateful for any assistance e.g., how to implement the scaling of the parameters per https://apmonitor.com/do/index.php/Main/ModelInitialization. Thank you!

Leaderboard
  • 371
  • 1
  • 10
Pavan Inguva
  • 65
  • 1
  • 7

1 Answers1

0

Try increasing the value of the final time until the solver can no-longer find a solution such as with tf=28 (successful). A plot of the solution reveals that Tin is adjusted to be zero at about the time where the solution almost fails to converge. I added a couple additional objective forms that didn't help the convergence (see Objective Method #1 and #2). The values of J, Vs, Vd are high but not unmanageable by the solver. One way to think about scaling is by changing units such as changing from kg/day to kg/s as the basis. Gekko automatically scales variables by the initial condition.

solution

from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt

#Parameters and IC
full_params = [0.027,2.12e-9,7.13e-3,168,168,0.035,1e-3]
full_x0 = [5e6,0.0,0.0,0.0,1.25e5,0.0]
mu,k1,k2,k3,k33,k4, f= full_params

#Initialize model
m = GEKKO()

#Time discretization
tf = 28
n_steps = tf*10+1
m.time = np.linspace(0,tf,n_steps)

#Define MVs
D = m.MV(value=0.1,lb=0.0,ub=0.1)
D.STATUS = 1
D.DCOST = 0.0
Tin = m.MV(value=1e7,lb=0,ub=1e7)
Tin.STATUS = 1
Tin.DCOST = 0.0

#Define States
T = m.Var(value=full_x0[0])
Id = m.Var(value=full_x0[1])
Is = m.Var(value=full_x0[2])
Ic = m.Var(value=full_x0[3])
Vs = m.Var(value=full_x0[4])
Vd = m.Var(value=full_x0[5])

#Define equations
m.Equation(T.dt() == mu*T -k1*(Vs+Vd)*T + D*(Tin-T))
m.Equation(Id.dt() == k1*Vd*T -(k1*Vs -mu)*Id -D*Id)
m.Equation(Is.dt() == k1*Vs*T -(k1*Vd + k2)*Is -D*Is)
m.Equation(Ic.dt() == k1*(Vs*Id + Vd*Is) -k2*Ic -D*Ic)
m.Equation(Vs.dt() == k3*Is - (k1*(T+Id+Is+Ic) + k4 + D)*Vs)
m.Equation(Vd.dt() == k33*Ic + f*k3*Is - (k1*(T+Id+Is+Ic) + k4 + D)*Vd)

# Original Objective
if True:
    J = m.Var(value=0) # objective (profit)
    Jf = m.FV() # final objective
    Jf.STATUS = 1
    m.Connection(Jf,J,pos2="end")
    m.Equation(J.dt() == D*(Vs + Vd))
    m.Obj(-Jf)

# Objective Method 1
if False:
    p=np.zeros_like(m.time); p[-1]=1
    final = m.Param(p)
    J = m.Var(value=0) # objective (profit)
    m.Equation(J.dt() == D*(Vs + Vd))
    m.Maximize(J*final)

# Objective Method 2
if False:
    m.Maximize(D*(Vs + Vd))

m.options.IMODE = 6  # optimal control
m.options.NODES = 2  # collocation nodes
m.options.SOLVER = 3
m.options.MAX_ITER = 10000

#Solve
m.solve()

plt.figure(figsize=(10,8))
plt.subplot(3,1,1)
plt.plot(m.time,Tin.value,'r.-',label='Tin')
plt.legend(); plt.grid()
plt.subplot(3,1,2)
plt.semilogy(m.time,T.value,label='T')
plt.semilogy(m.time,Id.value,label='Id')
plt.semilogy(m.time,Is.value,label='Is')
plt.semilogy(m.time,Ic.value,label='Ic')
plt.legend(); plt.grid()
plt.subplot(3,1,3)
plt.semilogy(m.time,Vs.value,label='Vs')
plt.semilogy(m.time,Vd.value,label='Vd')
plt.semilogy(m.time,J.value,label='Objective')
plt.legend(); plt.grid()
plt.show()

Is there any type of constraint in the problem that would favor a decrease at the end? This may be the cause of the infeasibility at tf=30. Another way to get a feasible solution is to solve with m.options.TIME_STEP=20 and resolve the problem with the initial conditions from the prior solution equal to the value at time step 20.

#Solve
m.solve()
m.options.TIME_SHIFT=20
m.solve()

This way, the solution steps forward in time to optimize in parts. This strategy was used to optimize a High Altitude Long Endurance (HALE) UAV and is called Receding Horizon Control.

Martin, R.A., Gates, N., Ning, A., Hedengren, J.D., Dynamic Optimization of High-Altitude Solar Aircraft Trajectories Under Station-Keeping Constraints, Journal of Guidance, Control, and Dynamics, 2018, doi: 10.2514/1.G003737.

John Hedengren
  • 12,068
  • 1
  • 21
  • 25
  • 1
    Thank you so much for this! From my understanding, there arent any model quirks or terms that would force Tin to zero. There is a Hopf bifurcation, but id be surprised if the oscillatory behavior makes convergence more challenging. When solving the simplified model, the solution is quite neat which is max Tin and max D which makes physical sense. But even when I supply these as initial guesses to the solver, it is not too happy with. I tried adding a DCOST to suppress this, but no luck. – Pavan Inguva Oct 07 '22 at 03:09