Parameter estimation in an ODE system using GEKKO - python

I have a system of nonlinear Ordinary Differential Equations that has 3 parameters as inputs: mu, target, and omega. I am trying the estimate these parameters by minimizing the least squared error between my observed values and the output of the ODE system (x5). I have tried different objective functions as well as different IMODES but the solution never converged. Besides, as I always get no solution found error, I cannot see what the closest best parameters are. Do you have any suggestions for my optimization problem? Thanks!
Here is my yData
My code is:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import pickle
with open('y_observed','rb') as f:
yData = pickle.load(f)
m = GEKKO()
dt = 0.001
m.time = np.arange(0,1.5,dt)
# Parameters
mu = m.FV(value=0.2, lb=-0.1, ub=1)
omega = m.FV(value=70, lb=0, ub=100)
target = m.FV(value=0.1, lb=-1, ub=3)
# Variables
x1 = m.Var(value=0)
x2 = m.Var(value=0.05)
x3 = m.Var(value=0)
x4 = m.Var(value=0)
x5 = m.Var(value=0.05)
x6 = m.Var(value=0)
y = m.Param(value = yData)
m.Minimize((y-x5)**2)
# Constants
A = 75
B = 70
C = 45
# ODEs
m.Equation(x1.dt()== 1-x1)
m.Equation(x2.dt()== x3)
m.Equation(x3.dt()== x1*70*(x1*B*0.25*(target-x2) - x3))
m.Equation(x4.dt()== C*(mu - x4))
m.Equation(x5.dt()== A*1.0/np.abs(mu)*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(x6.dt()== A*1.0/np.abs(mu)*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
#Application options
m.options.SOLVER = 3 #APOPT solver
m.options.IMODE = 5 #Dynamic Simultaneous - estimation
m.options.EV_TYPE = 2 #absolute error
#m.options.NODES = 3 #collocation nodes (2,5)
if True:
mu.STATUS=1
target.STATUS=1
omega.STATUS=1
m.solve(disp=True)
print('Objective: ' + str((x5-yData)**2))
print('Solution')
print('mu = ' + str(mu.value[0]))
print('omega = ' + str(omega.value[0]))
print('target = ' + str(target.value[0]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,x5.value,'k:',LineWidth=2,label=r'$x_5$')
plt.plot(m.time,yData,'b-',LineWidth=2,label=r'$yData$')
plt.ylabel('Value')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(m.time,mu.value,'r-',LineWidth=2,label=r'$mu$')
plt.plot(m.time,target.value,'r-',LineWidth=2,label=r'$target$')
plt.plot(m.time,omega.value,'r-',LineWidth=2,label=r'$omega$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()

Use a Gekko function (m.abs2() or m.abs3()) for absolute value instead of the Numpy np.abs() function.
amu = m.abs3(mu)
This requires a switch to the APOPT solver m.options.SOLVER=1 to ensure that the Mixed Integer part of the model is satisfied. Also, it avoids divide by zero by reformulating your equations as:
m.Equation(amu*x5.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(amu*x6.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
The full problem has quite a few variables:
Number of state variables: 47971
Number of total equations: - 44970
Number of slack variables: - 5996
---------------------------------------
Degrees of freedom : -2995
You can ignore the Warning: DOF <= 0 because not all of the inequality constraints are active. This gives a successful solution that may be a local solution. It may require better parameter initial guess to get global solution such as if you need it to follow the cyclic data.
Objective: 260.68493685
Solution
mu = 0.0
omega = 70.0
target = 0.13906027337
Here is the full source code with results that use your PKL file for retrieving the data.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import pickle
with open('y_observed.pkl','rb') as f:
yData = pickle.load(f)
m = GEKKO()
dt = 0.001
m.time = np.arange(0,1.5,dt)
# Parameters
mu = m.FV(value=0.2, lb=-0.1, ub=1)
omega = m.FV(value=70, lb=0, ub=100)
target = m.FV(value=0.1, lb=-1, ub=3)
# Variables
x1 = m.Var(value=0)
x2 = m.Var(value=0.05)
x3 = m.Var(value=0)
x4 = m.Var(value=0)
x5 = m.Var(value=0.05)
x6 = m.Var(value=0)
y = m.Param(value = yData)
m.Minimize((y-x5)**2)
# Constants
A = 75
B = 70
C = 45
# abs(mu)
amu = m.abs3(mu)
# ODEs
m.Equation(x1.dt()== 1-x1)
m.Equation(x2.dt()== x3)
m.Equation(x3.dt()== x1*70*(x1*B*0.25*(target-x2) - x3))
m.Equation(x4.dt()== C*(mu - x4))
m.Equation(amu*x5.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(amu*x6.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
#Application options
m.options.SOLVER = 1 # APOPT MINLP solver
m.options.IMODE = 5 # Dynamic Simultaneous - estimation
m.options.EV_TYPE = 2 # squared error
m.options.NODES = 3 #collocation nodes (2,5)
if True:
mu.STATUS=1
target.STATUS=1
omega.STATUS=1
m.solve(disp=True)
print('Objective: ' + str(m.options.OBJFCNVAL))
print('Solution')
print('mu = ' + str(mu.value[0]))
print('omega = ' + str(omega.value[0]))
print('target = ' + str(target.value[0]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,x5.value,'k:',lw=2,label=r'$x_5$')
plt.plot(m.time,yData,'b-',lw=2,label=r'$yData$')
plt.ylabel('Value')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(m.time,mu.value,'b-',lw=2,label=r'$mu$')
plt.plot(m.time,target.value,'r--',lw=2,label=r'$target$')
plt.plot(m.time,omega.value,'g:',lw=2,label=r'$omega$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
One of the complicated things about this problem is allowing mu to be negative. It is easier to solve if you give it a lower bound of a small number (such as 0.01) to avoid the mu=0 solution.
# Parameters
mu = m.FV(value=0.2, lb=0.01, ub=1)
Objective: 260.68477646
Solution
mu = 0.042062519923
omega = 69.992948651
target = 0.13871376807

Related

Odeint problem in model: lsoda-- repeated occurrences of illegal input && lsoda-- at start of problem, too much accuracy

I need help to solve a ODE system using odeint or any workaround that works. I`m trying to model a ODE system descriving the beheaviour of a cilinder on a mechanichal arm, using Lagrangian mechanics I got the following 2nd order ODE system:
$$\ddot{x} = x*\dot{\phi}^2 - g*(sin(\phi) +cos(\phi))$$
$$\ddot{\phi} = \frac{1}{2*\dot{x}*m_c}*(\frac{k*i}{x}-\frac{m_b*L*\dot{\phi}}{2}-g*cos(\phi)*(m_c*x-m_b*L/2))$$
Then I transformed it to a 4x4 1st order ODE system y linearized 2 equations aroun initial condition, to make it more "solveable".
In python I`wrote the folllowing:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# source: https://apmonitor.com/pdc/index.php/Main/SolveDifferentialEquations
g, mc, mb, k, L = (9.8, 0.5, 1.3, 2, 1.2)
def model(z, t, i, z0):
global g, mc, mb, k, L
#ODE variables
x1 = z[0]
y1 = z[1]
x2 = z[2]
y2 = z[3]
#ODE initial conditions
zx1 = z0[0]
zy1 = z0[1]
zx2 = z0[2]
zy2 = z0[3]
#Diferential for linealization around I.C.
dx1 = z[0]-z0[0]
dy1 = z[1]-z0[1]
dx2 = z[2]-z0[2]
dy2 = z[3]-z0[3]
def w1(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return zx2*zy1**2-g*(np.sin(zy2)+np.cos(zy2))
def w2(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return 1/(2*zx1*mc)*((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))
dx2dt = x1
#dx1dt = x2*y1**2-g*(np.sin(y2)+np.cos(y2))
dx1dt = w1(zx1, zy1, zx2, zy2) + zy1**2*dx2+2*zx2 * \
zy1*dy1-g*(np.cos(zy2)-np.sin(zy2))*dy2
dy2dt = y1
#dy1dt = 1/(2*x1*mc)*((k*i)/(x2)-mb*y2*L/2-g*np.cos(y2*(mc*x2-mb*L/2)))
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))*dx1
+(k*i*np.log(abs(zx2))/(2*zx1*mc)-g*np.cos(zy2)*mc)*dx2-(mb*L) / \
(4*zx1*mc)*dy1+g*np.sin(zy2)*(mc*zx2-mb*L/2)*(2*zx1*mc)*dy2
dzdt = [dx1dt, dy1dt, dx2dt, dy2dt]
return dzdt
# initial condition
z0 = [0.01, 0.01, 0.8, np.pi/8]
# number of time points
n = 30
# time points
t = np.linspace(0, 1, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[:] = 1
# store solution
x1 = np.zeros(n)
y1 = np.zeros(n)
x2 = np.zeros(n)
y2 = np.zeros(n)
# record initial conditions
x1[0] = z0[0]
y1[0] = z0[1]
x2[0] = z0[2]
y2[0] = z0[3]
# solve ODE
for i in range(1, n):
# span for next time step
tspan = [t[i-1], t[i]]
# solve for next step
z = odeint(model, z0, tspan, args=((u[i], z0)) )
# store solution for plotting
if -np.pi/4 <= z[1][3] <= np.pi/4: # Angulo +/- Pi/4
u[i] = u[i-1] + 1
else:
u[i] = u[i-1]
if 0 <= z[1][2] <= L: # Se mantiene en el brazo
print("Se salió")
x1[i] = z[1][0]
y1[i] = z[1][1]
x1[i] = z[1][2]
y1[i] = z[1][3]
# Siguientes CI
z0 = z[1]
# plot results
plt.plot(t, u, 'g:', label='u(t)')
plt.plot(t, x2, 'b-', label='x(t)')
plt.plot(t, y2, 'r--', label='phi(t)')
plt.ylabel('Cilindro(x,phi)')
plt.xlabel('Tiempo(s)')
plt.legend(loc='best')
plt.show()
Then I`ve got the following error, telling me than too much accuracy is needed to solve my system:
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
lsoda-- repeated occurrences of illegal input
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.3448275862069D+00 r2 = 0.1555030227910D-28

Is there any way to optimize time and path simultaneously?

I have a problem with time and path optimization. I couldn't define two objectives for both time and path simultaneously. Python reads the last objective and gives result according to that way.
Could you please help me to solve this optimization problem? Thanks..
import matplotlib.pyplot as plt
from gekko import GEKKO
# Gekko model
m = GEKKO(remote=False)
# Time points
nt = 501 # nt=101
tm = np.linspace(0, 1, nt) # tm = np.linspace(0, 100, nt)
m.time = tm
# Variables
g = m.Const(value=9.80665)
V = m.Const(value=200) # velocity
Xi = m.Var(value=0, lb=-2*np.pi, ub=2*np.pi) # Heading angle value=-np.pi dene
x = m.Var(value=0, lb=-100000, ub=100000) # x position
y = m.Var(value=0, lb=-100000, ub=100000) # y position
pathx = m.Const(value=70000) # intended distance in x direction
pathy = m.Const(value=20000) # intended distance in y direction
p = np.zeros(nt) # final time=1
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER = 1000000 # iteration number
# Optimize Final Time
tf = m.FV(value=1.0, lb=0.0001, ub=1000.0)
tf.STATUS = 1
# Controlled parameters
Mu = m.MV(value=0, lb=-1, ub=1) # solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
# Equations
m.Equation(x.dt() == tf * (V * (m.cos(Xi))))
m.Equation(y.dt() == tf * (V * (m.sin(Xi))))
m.Equation(Xi.dt() == tf * (g * m.tan(Mu)) / V )
# Objective Function
w = 1e4
m.Minimize(w * (x * final - pathx) ** 2) # 1D part (x)
m.Minimize(w * (pathy - y * final) ** 2) # 2D part (y)
m.Obj(tf)
'''
Multiple objectives are combined into a single objective with summation in Gekko. If there are multiple separate objectives that should be considered separately then a Pareto front is a possible approach to evaluate the tradeoff (see Section 6.5 of the Optimization book). However, this isn't implemented in Gekko.
One correct is that m.Equation(y * final <= pathy ) should be m.Equation((y-pathy) * final <= 0) to avoid a potential infeasible solution if pathy<0.
import matplotlib.pyplot as plt
from gekko import GEKKO
import numpy as np
# Gekko model
m = GEKKO(remote=False)
# Time points
nt = 51 # nt=101
tm = np.linspace(0, 1, nt) # tm = np.linspace(0, 100, nt)
m.time = tm
# Variables
g = m.Const(value=9.80665)
V = m.Const(value=200) # velocity
Xi = m.Var(value=0, lb=-2*np.pi, ub=2*np.pi) # Heading angle value=-np.pi dene
x = m.Var(value=0, lb=-100000, ub=100000) # x position
y = m.Var(value=0, lb=-100000, ub=100000) # y position
pathx = m.Const(value=70000) # intended distance in x direction
pathy = m.Const(value=20000) # intended distance in y direction
p = np.zeros(nt) # final time=1
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER = 1000000 # iteration number
# Optimize Final Time
tf = m.FV(value=1.0, lb=0.0001, ub=1000.0)
tf.STATUS = 1
# Controlled parameters
Mu = m.MV(value=0, lb=-1, ub=1) # solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
# Equations
m.Equation(x.dt() == tf * (V * (m.cos(Xi))))
m.Equation(y.dt() == tf * (V * (m.sin(Xi))))
m.Equation(Xi.dt() == tf * (g * m.tan(Mu)) / V )
m.Equation((x-pathx) * final <= 0)
m.Equation((y-pathy) * final <= 0)
# Objective Function
w = 1e4
m.Minimize(w * (x * final - pathx) ** 2) # 1D part (x)
m.Minimize(w * (y * final - pathy) ** 2) # 2D part (y)
#in here python reads the last objective how can i run this two objectives simultaneously.
#m.Obj(Xi * final)
m.Minimize(tf)
#m.Maximize(0.2 * mass * final) # objective function
m.options.IMODE = 6
#m.options.NODES = 2
#m.options.MV_TYPE = 1
#m.options.SOLVER = 3
# m.open_folder() # to search for infeasibilities
m.solve(disp=False)
print('Final Time: ' + str(tf.value[0]))
tm = np.linspace(0,tf.value[0],nt)
#tm = tm * tf.value[0]
fig, axs = plt.subplots(3)
fig.suptitle('Results')
axs[0].plot(tm, Mu.value, 'b-', lw=2, label=r'$Bank$')
axs[0].legend(loc='best')
axs[1].plot(tm, x.value, 'r--', lw=2, label=r'$x$')
axs[1].legend(loc='best')
axs[2].plot(tm, y.value, 'p-', lw=2, label=r'$y$')
axs[2].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
plt.figure()
plt.plot(x.value, y.value)
plt.show()

Scipy.optimize.minimize() two arrays for non-linear objective

I have fit a lognormal model, and I would now like to use the coefficients from that model to find the combination of variable values that will provide the greatest response. I intend on introducing some constraints later, but for now I would just like to get the optimization running. The complicating issue is that the model was a mixed methods model, so I have coefficients for each individual in the model. As a result, I need to optimize the variable values for each individual, given their individual coefficients.
My example:
import numpy as np
from scipy.optimize import minimize
def objective(x, beta):
x1, x2 = x
beta1, beta2 = beta
return -1 * np.sum(np.exp(3 + x1*beta1 + x2*beta2))
# initial guesses for variables x1 and x2
n = 2
x1 = np.zeros(n)
x1[0] = 1.0
x1[1] = 2.0
x2 = np.zeros(n)
x2[0] = 3.0
x2[1] = 4.0
x0 = np.vstack((x1,x2))
# the coefficients (weights) for each of n individuals, in each variable
beta1 = np.zeros(n)
beta1[0] = 1.1
beta1[1] = 1.01
beta2 = np.zeros(n)
beta2[0] = 1.1
beta2[1] = 1.01
beta0 = np.vstack((beta1, beta2))
# show initial objective
print('Initial SSE Objective: ' + str(objective(x0, beta0))) # this works as intended
# but I'm not sure how to specify bounds given my shape
b = (1.0,5.0) #min = 1, max = 5 on any one channel for any one individual
bnds = (b, b)
# running without bounds
solution = minimize(objective, x0, method='SLSQP',
args=beta0)
...gives the following error;
File "<ipython-input-92-554f967ca90b>", line 2, in objective
x1, x2 = x
ValueError: too many values to unpack (expected 2)
Given that the objective function works if I pass it the args, is this a limitation of minimize() ? Is the function unable to take x in shape (2,2)?
Also, I'm having a hard time specifying the bounds correctly...
# running with bounds
solution = minimize(objective, x0, method='SLSQP',
args=beta0, bounds=bnds)
...gives the error;
ValueError: operands could not be broadcast together with shapes (4,) (2,) (2,)
I was able to do this with gekko DOCS HERE
from gekko import GEKKO
m = GEKKO() # Initialize gekko
n = 2
# Init the coefficients for each HCP
alpha_list = np.random.normal(3, 0.1, n)
beta1_list = np.random.normal(1.01, 0.1, n)
beta2_list = np.random.normal(1.02, 0.1, n)
beta3_list = np.random.normal(1.04, 0.1, n)
# Initialize variables
x1 = [m.Var(value=1,lb=0,ub=4) for i in range(n)]
x2 = [m.Var(value=1,lb=0,ub=4) for i in range(n)]
x3 = [m.Var(value=1,lb=0,ub=4) for i in range(n)]
# Init the coefficients
alpha = [m.Const(value=alpha_list[i]) for i in range(n)]
beta1 = [m.Const(value=beta1_list[i]) for i in range(n)]
beta2 = [m.Const(value=beta2_list[i]) for i in range(n)]
beta3 = [m.Const(value=beta3_list[i]) for i in range(n)]
# Inequality constraints
m.Equation(m.sum(x1) + m.sum(x2) + m.sum(x3) <= n*10)
m.Obj(-1 * m.sum([m.exp(alpha[i] + x1[i]*beta1[i] + x2[i]*beta2[i] + x3[i]*beta3[i]) for i in range(n)])) # Objective
m.options.IMODE = 3 # Steady state optimization set to 3, change to 1 for integer
m.options.MAX_ITER = 1000
m.solve() # Solve
print('Results')
print('x1: ' + str(x1))
print('x2: ' + str(x2))
print('x3: ' + str(x3))
...but for my own learning I'd like to be able to do this with scipy too.
I'm reporting back in case someone else ever runs in to the same problem. I think the limitation is that the input from minimize to the objective function must be a flat array, so my (2,2) shape wouldn't work (correct me if I'm wrong folks).
So, a workaround is to input a flat array, and then unpack it in the objective function itself. If you hand the function the number of individuals to expect, the function can process the arrays so that each channel gets a 1xN as input to the regression equations we're trying to optimize.
import numpy as np
from scipy.optimize import minimize
def objective(x, beta, n):
x1, x2 = x.reshape(2,n)
beta1, beta2 = beta.reshape(2,n)
return -1 * np.sum(np.exp(3 + x1*beta1 + x2*beta2))
# initial guesses for variables x1 and x2
n = 2
x1 = np.zeros(n)
x1[0] = 1.0
x1[1] = 2.0
x2 = np.zeros(n)
x2[0] = 3.0
x2[1] = 4.0
x0 = np.concatenate((x1,x2))
# the coefficients (weights) for each of n individuals, in each variable
beta1 = np.zeros(n)
beta1[0] = 1.1
beta1[1] = 1.01
beta2 = np.zeros(n)
beta2[0] = 1.1
beta2[1] = 1.01
beta0 = np.concatenate((beta1, beta2))
# show initial objective
print('Initial SSE Objective: ' + str(objective(x0, beta0, 2))) # this works as intended
# specifying bounds is much easier now we just have a flat array
b = (1.0,5.0) #min = 1, max = 5 on any one channel for any one individual
bnds = (b , b)*n
# running with bounds
solution = minimize(objective, x0, method='SLSQP',
args=(beta0,n), bounds=bnds)
x = solution.x
# show final objective
print('Final SSE Objective: ' + str(objective(x, beta0, n)))
# print solution
print('Solution')
x_sol = x.reshape(2,n)
print('x1 = ' + str(x_sol[0]))
print('x2 = ' + str(x_sol[1]))
As expected, without constraints, the minimize call just maxes out the value of each variable in the equation. My next step will be to put constraints on this function.

Forming system of constraint equations using GEKKO optimization framework

I'm trying to solve a simple minimum time optimal control problem using double integrator dynamics of the form,
dx1/dt = x2
dx2/dt = u
with the GEKKO optimization framework as follows:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
model = GEKKO(remote=False)
x1_initial = 0.0
x1_final = 10.0
x2_initial = 0.0
x2_final = 0.0
t_initial = 0.0
t_final = 25.0
num_timesteps = 1000
dt = (t_final - t_initial) / num_timesteps
x = model.Array(model.Var, (2, num_timesteps + 1))
u = model.Array(model.Var, num_timesteps + 1)
tf = model.Var()
for k in range(num_timesteps + 1):
u[k].lower = -0.4
u[k].upper = 0.4
u[k].value = 0.0
for k in range(num_timesteps + 1):
x[0, k].value = 5.0
x[1, k].value = 0.0
tf.lower = t_initial
tf.upper = t_final
tf.value = t_final
dt = (tf - t_initial) / num_timesteps
def f(x, u, k):
return np.array([x[1,k], u[k]])
for k in range(num_timesteps):
model.Equations([x[:, k + 1] == x[:, k] + (dt/2.0)*(f(x, u, k + 1) + f(x, u, k))])
# model.Equation(x[0, k + 1] == x[0, k] + (dt/2.0)*(x[1, k + 1] + x[1, k]))
# model.Equation(x[1, k + 1] == x[1, k] + (dt/2.0)*(u[k + 1] + u[k]))
model.Equation(x[0, 0] == x1_initial)
model.Equation(x[0, num_timesteps] == x1_final)
model.Equation(x[1, 0] == x2_initial)
model.Equation(x[1, num_timesteps] == x2_final)
model.Minimize(tf)
model.options.solver = 3
model.solve()
# Plotting results
t = np.linspace(t_initial, tf.value, num_timesteps + 1)
u_optimal = []
for k in range(num_timesteps + 1):
u_optimal.append(u[k].value)
x1_optimal = []
for k in range(num_timesteps + 1):
x1_optimal.append(x[0, k].value)
x2_optimal = []
for k in range(num_timesteps + 1):
x2_optimal.append(x[1, k].value)
plt.figure()
plt.plot(t, u_optimal)
plt.xlabel('time (s)')
plt.ylabel('u(t)')
plt.grid()
plt.figure()
plt.plot(t, x1_optimal)
plt.xlabel('time (s)')
plt.ylabel('x1(t)')
plt.grid()
plt.figure()
plt.plot(t, x2_optimal)
plt.xlabel('time (s)')
plt.ylabel('x2(t)')
plt.grid()
plt.show()
What I'm trying to do is to form a system of equality constraints using trapezoidal integration and then solve this system for the optimal control inputs using GEKKO. However, using the function definition,
def f(x, u, k):
return np.array([x[1,k], u[k]])
in conjunction with the system of equality constraints,
for k in range(num_timesteps):
model.Equations([x[:, k + 1] == x[:, k] + (dt/2.0)*(f(x, u, k + 1) + f(x, u, k))])
gives me the following error,
Exception: #error: Equation Definition
Equation without an equality (=) or inequality (>,<)
false
STOPPING...
I've added two commented lines of code in the above code snippet that will allow the program to run correctly, but I'm hoping to avoid having to separate each equation out, since I'd like to extend this to problems that deal with more complicated system dynamics, and to also use more sophisticated collocation methods instead of the trapezoidal approach.
I know that GEKKO has some nice features for dynamic optimization, but I'm looking to try and implement various direct collocation methods myself to understand the theory a bit better.
There are some examples of orthogonal collocation on finite elements in the Machine Learning and Dynamic Optimization course.
from __future__ import division
import numpy as np
from scipy.optimize import fsolve
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# final time
tf = 1.0
# solve with ODEINT (for comparison)
def model(x,t):
u = 4.0
return (-x**2 + u)/5.0
t = np.linspace(0,tf,20)
y0 = 0
y = odeint(model,y0,t)
plt.figure(1)
plt.plot(t,y,'r-',label='ODEINT')
# ----------------------------------------------------
# Approach #1 - Write the model equations in Python
# ----------------------------------------------------
# define collocation matrices
def colloc(n):
if (n==2):
NC = np.array([[1.0]])
if (n==3):
NC = np.array([[0.75,-0.25], \
[1.00, 0.00]])
if (n==4):
NC = np.array([[0.436,-0.281, 0.121], \
[0.614, 0.064, 0.0461], \
[0.603, 0.230, 0.167]])
if (n==5):
NC = np.array([[0.278, -0.202, 0.169, -0.071], \
[0.398, 0.069, 0.064, -0.031], \
[0.387, 0.234, 0.278, -0.071], \
[0.389, 0.222, 0.389, 0.000]])
if (n==6):
NC = np.array([[0.191, -0.147, 0.139, -0.113, 0.047],
[0.276, 0.059, 0.051, -0.050, 0.022],
[0.267, 0.193, 0.252, -0.114, 0.045],
[0.269, 0.178, 0.384, 0.032, 0.019],
[0.269, 0.181, 0.374, 0.110, 0.067]])
return NC
# define collocation points from Lobatto quadrature
def tc(n):
if (n==2):
time = np.array([0.0,1.0])
if (n==3):
time = np.array([0.0,0.5,1.0])
if (n==4):
time = np.array([0.0, \
0.5-np.sqrt(5)/10.0, \
0.5+np.sqrt(5)/10.0, \
1.0])
if (n==5):
time = np.array([0.0,0.5-np.sqrt(21)/14.0, \
0.5,0.5+np.sqrt(21)/14.0, 1])
if (n==6):
time = np.array([0.0, \
0.5-np.sqrt((7.0+2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5-np.sqrt((7.0-2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5+np.sqrt((7.0-2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5+np.sqrt((7.0+2.0*np.sqrt(7.0))/21.0)/2.0, \
1.0])
return time*tf
# solve with SciPy fsolve
def myFunction(z,*param):
n = param[0]
m = param[1]
# rename z as x and xdot variables
x = np.empty(n-1)
xdot = np.empty(n-1)
x[0:n-1] = z[0:n-1]
xdot[0:n-1] = z[n-1:m]
# initial condition (x0)
x0 = 0.0
# input parameter (u)
u = 4.0
# final time
tn = tf
# function evaluation residuals
F = np.empty(m)
# nonlinear differential equations at each node
# 5 dx/dt = -x^2 + u
F[0:n-1] = 5.0 * xdot[0:n-1] + x[0:n-1]**2 - u
# collocation equations
# tn * NC * xdot = x - x0
NC = colloc(n)
F[n-1:m] = tn * np.dot(NC,xdot) - x + x0 * np.ones(n-1)
return F
sol_py = np.empty(5) # store 5 results
for i in range(2,7):
n = i
m = (i-1)*2
zGuess = np.ones(m)
z = fsolve(myFunction,zGuess,args=(n,m))
# add to plot
yc = np.insert(z[0:n-1],0,0)
plt.plot(tc(n),yc,'o',markersize=10,label='Nodes = '+str(i))
# store just the last x[n] value
sol_py[i-2] = z[n-2]
plt.legend(loc='best')
# ----------------------------------------------------
# Approach #2 - Write model in APMonitor and let
# modeling language create the collocation equations
# ----------------------------------------------------
# load GEKKO
from gekko import GEKKO
sol_apm = np.empty(5) # store 5 results
i = 0
for nodes in range(2,7):
m = GEKKO(remote=False)
u = m.Param(value=4)
x = m.Var(value=0)
m.Equation(5*x.dt() == -x**2 + u)
m.time = [0,tf]
m.options.imode = 4
m.options.time_shift = 0
m.options.nodes = nodes
m.solve() # solve problem
sol_apm[i] = x.value[-1] # store solution (last point)
i += 1
# print the solutions
print(sol_py)
print(sol_apm)
# show plot
plt.ylabel('x(t)')
plt.xlabel('time')
plt.show()
You can define variables with the same name (such as x) or use m.Array(m.Var,n) to define the variables. One thing to look at is the model file by opening the run folder with m.open_folder() before you send the m.solve() command. Look at the .apm file in that folder with a text editor.

How to implement a system of stochastic ODEs (SDEs) in python?

I have a system of ODEs in which I am trying to include an 'error' term, so that it becomes a system of stochastic ODEs.
For solving a system of ODEs in python I normally use scipy's odeint.
An example derived from the Scipy Cookbook, involving the famous Zombie apocalypse:
# zombie apocalypse modeling
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
plt.rcParams['figure.figsize'] = 10, 8
P = 0 # birth rate
d = 0.0001 # natural death percent (per day)
B = 0.0095 # transmission percent (per day)
G = 0.0001 # resurect percent (per day)
A = 0.0001 # destroy percent (per day)
# solve the system dy/dt = f(y, t)
def f(y, t):
Si = y[0]
Zi = y[1]
Ri = y[2]
# the model equations (see Munz et al. 2009)
f0 = P - B*Si*Zi - d*Si
f1 = B*Si*Zi + G*Ri - A*Si*Zi
f2 = d*Si + A*Si*Zi - G*Ri
return [f0, f1, f2]
# initial conditions
S0 = 500. # initial population
Z0 = 0 # initial zombie population
R0 = 0 # initial death population
y0 = [S0, Z0, R0] # initial condition vector
t = np.linspace(0, 5., 1000) # time grid
# solve the DEs
soln = odeint(f, y0, t)
S = soln[:, 0]
Z = soln[:, 1]
R = soln[:, 2]
# plot results
plt.figure()
plt.plot(t, S, label='Living')
plt.plot(t, Z, label='Zombies')
plt.xlabel('Days from outbreak')
plt.ylabel('Population')
plt.title('Zombie Apocalypse - No Init. Dead Pop.; No New Births.')
plt.legend(loc=0)
plt.show()
Is it possible to use odeint to solve a system of stochastic ODEs?
For example if I would like to include an error term/random walk in the birth rate (P) of the equations?
My idea was to use an extra equation in the system to be able to define a random walk (randomly sampled death rate (using random.normalvariate()) and to solve the system like this:
f0 = P - B*Si*Zi - f3*Si
f1 = B*Si*Zi + G*Ri - A*Si*Zi
f2 = f3*Si + A*Si*Zi - G*Ri
f3 = random.normalvariate(mu, sigma)
return [f0, f1, f2]
Is this the right way to solve a system of SDEs? Or do I have to use a different solver for stochastic ODEs?
With help the system of ODEs was rewriten into an system of SDEs in which the birth rate was a stochastic process.
It was a great suggestion to use SDEint package.
# Zombie apocalypse SDE model
import matplotlib.pyplot as plt
import numpy as np
import sdeint
P, d, B, G, A = 0.0001, 0.0001, 0.0095, 0.0001, 0.0001
tspan = np.linspace(0, 5., 1000)
y0 = np.array([500., 0., 0., P])
def f(y, t):
Si = y[0]
Zi = y[1]
Ri = y[2]
f0 = y[3] - B * Si * Zi - d * Si
f1 = B * Si * Zi + G * Ri - A * Si * Zi
f2 = d * Si + A * Si * Zi - G * Ri
f3 = 0
return np.array([f0, f1, f2, f3])
def GG(y, t):
return np.diag([0, 0, 0, 100])
result = sdeint.itoint(f, GG, y0, tspan)
plt.plot(result)
plt.show()

Categories