I need to fit the sum of two populations defined by two different partial equations depending onto 3 parameters to a list of measurements of type (mesurement, time) using GEKKO. The differential equations are:
dS/dt = (a - b) * S
dR/dt = (a - b - c)* R
X(t) = S(t) + R(t)
(a,b,c are parameters)
I need to fit X to the measurements
Here are sample measured X
values:
# data
m.time = [0,0.1,0.2,0.3,0.4,0.5,0.8,0.85,0.9,0.95,1]
x_meas = [1.1,1.2,1.56,1.77,1.89,2.1,2.3,2.5,2.7,2.2,2.1]
Gekko adjusts the parameters a
, b
, and c
.
# parameters, single value over time horizon
p = m.Array(m.FV,3,lb=0.5,ub=10); a,b,c = p
# turn STATUS On for adjustment by solver
for fv in p:
fv.STATUS=1
The value of X
is calculated with differential equations for S
and R
and an algebraic equation with X=S+R
.
# variables
S,R,X = m.Array(m.Var,3,lb=0)
# change initial conditions
S0,R0 = [0.35,0.65]; X0 = S0+R0
S.value=S0; R.value=R0; X.value=X0
# equations
m.Equations([S.dt()==(a-b)*S,
R.dt()==(a-b-c)*R,
X==S+R])
The squared error between the measured X
values and the predicted X
value is minimized.
# minimize difference to measurements
m.Minimize((X-Xm)**2)
A few configuration parameters are needed to run in dynamic estimation mode (IMODE=5
) and with 3 collocation nodes per time step (NODES=3
).
m.options.IMODE = 5 # dynamic estimation
m.options.NODES = 3 # collocation nodes
m.solve(disp=False) # display solver output
This produces an optimized solution that minimizes the squared error, but is not unique because a
and b
are co-linear parameters. The solver can increase a
and b
and get the same answer.
Here is the complete script:
from gekko import GEKKO
m = GEKKO(remote=False)
# data
m.time = [0,0.1,0.2,0.3,0.4,0.5,0.8,0.85,0.9,0.95,1]
x_meas = [1.1,1.2,1.56,1.77,1.89,2.1,2.3,2.5,2.7,2.2,2.1]
# parameters, single value over time horizon
p = m.Array(m.FV,3,lb=0.5,ub=10); a,b,c = p
# turn STATUS On for adjustment by solver
for fv in p:
fv.STATUS=1
# variables
S,R,X = m.Array(m.Var,3,lb=0)
# change initial conditions
S0,R0 = [0.35,0.65]; X0 = S0+R0
S.value=S0; R.value=R0; X.value=X0
# measured values
Xm = m.Param(x_meas)
# equations
m.Equations([S.dt()==(a-b)*S,
R.dt()==(a-b-c)*R,
X==S+R])
# minimize difference to measurements
m.Minimize((X-Xm)**2)
m.options.IMODE = 5 # dynamic estimation
m.options.NODES = 3 # collocation nodes
m.solve(disp=False) # display solver output
print(f'a: {a.value[0]}, b: {b.value[0]}, c: {c.value[0]}')
import matplotlib.pyplot as plt
plt.figure(figsize=(7,3))
plt.plot(m.time,S.value,'b:',linewidth=3,label='S')
plt.plot(m.time,R.value,'r--',linewidth=2,label='R')
plt.plot(m.time,X.value,'k--',label='X')
plt.plot(m.time,Xm.value,'kx',label='Xm')
plt.legend(); plt.grid(); plt.xlabel('Time')
plt.tight_layout()
plt.savefig('fit.png',dpi=300)
plt.show()
Additional example problems are available from the Dynamic Optimization course.