Search code examples
pythoncurve-fittingdifferential-equationsgekko

Fitting two populations to measurements using GEKKO


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


Solution

  • 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.

    fit data

    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.