Search code examples
optimizationodeleast-squaresscilab

nonlinear ODEs optimization with leastsq


[UPDATED] I'm working on a nonlinear ODEs system optimization and fitting it to experimental data. I have a system of 5 model ODEs which must be optimized by 17 parameters. My approach is to calculate the differences between solved ODEs and experimental data - function Differences, then use leastsq solver to minimize diferences and find the optimal parameters, as below code:

//RHSs of ODEs to be fitted:

function dx=model3(t,x,Kap,Ksa,Ko,Ks,Kia,Kis,p_Amax,q_Amax,qm,q_Smax,Yas,Yoa,Yxa,Yem,Yos,Yxsof,H)
    X=x(1);
    S=x(2);
    A=x(3);
    DO=x(4);
    V=x(5);`
    
    qs=((q_Smax*S/(S+Ks))*Kia/(Kia+A));
    qsof=(p_Amax*qs/(qs+Kap));
    qsox=(qs-qsof)*DO/(DO+Ko);
    qsa=(q_Amax*A/(A+Ksa))*(Kis/(qs+Kis));
    pa=qsof*Yas;
    qa=pa-qsa;
    qo=(qsox-qm)*Yos+qsa*Yoa;
    u=(qsox-qm)*Yem+qsof*Yxsof+qsa*Yxa;
    
    dx(1)=u*X-F*X/V;
    dx(2)=(F*(Sf-S)/V)-qs*X;
    dx(3)=qsa*X-(F*A/V);
    dx(4)=200*(100-DO)-qo*X*H;
    dx(5)=F;
endfunction

//experimental data:
//Dat=fscanfMat('dane_exper_III_etap.txt');

Dat = [
0   30  1.4 24.1    99  6884.754
1   35  0.2 23.2    89  6959.754
2   40  0.1 21.6    80  7034.754
3   52  0.1 19.5    67  7109.754
4   61  0.1 18.7    70  7184.754
5   66  0.1 16.4    79  7259.754
6   71  0.1 15      94  7334.754
7   74  0   14.3    100 7409.754
8   76  0   13.8    100 7484.754
9   78  0   13.4    100 7559.754
9.5 79  0   13.2    100 7597.254
10  79  0   13.5    100 7634.754]

t=Dat(:,1);
x_exp(:,1)=Dat(:,2);
x_exp(:,2)=Dat(:,3);
x_exp(:,3)=Dat(:,4);
x_exp(:,4)=Dat(:,5);
x_exp(:,5)=Dat(:,6);

global MYDATA;
MYDATA.t=t;
MYDATA.x_exp=x_exp;
MYDATA.funeval=0;


//calculating differences between calculated values and experimental data:

function f=Differences(k)
    global MYDATA
    t=MYDATA.t;
    x_exp=MYDATA.x_exp;
    Kap=k(1); //g/L
    Ksa=k(2); //g/L
    Ko=k(3); //g/L
    Ks=k(4); //g/L
    Kia=k(5); //g/L
    Kis=k(6); //g/L
    p_Amax=k(7); //g/(g*h)
    q_Amax=k(8); //g/(g*h)
    qm=k(9);
    q_Smax=k(10);
    Yas=k(11); //g/g
    Yoa=k(12);
    Yxa=k(13);
    Yem=k(14);
    Yos=k(15);
    Yxsof=k(16);
    H=k(17);
    x0=x_exp(1,:);
    t0=0;
    F=75;
    Sf=500;
    %ODEOPTIONS=[1,0,0,%inf,0,2,10000,12,5,0,-1,-1]
    x_calc=ode('rk',x0',t0,t,list(model3,Kap,Ksa,Ko,Ks,Kia,Kis,p_Amax,q_Amax,qm,q_Smax,Yas,Yoa,Yxa,Yem,Yos,Yxsof,H));
    diffmat=x_calc'-x_exp;
    //column vector of differences (concatenates 4 columns of the difference matrix)
    f=diffmat(:);
    MYDATA.funeval=MYDATA.funeval+1;
endfunction

// Initial guess
Kap=0.3; //g/L
Ksa=0.05; //g/L
Ko=0.1; //g/L
Ks=0.5; //g/L
Kia=0.5; //g/L
Kis=0.05; //g/L
p_Amax=0.4; //g/(g*h)
q_Amax=0.8; //g/(g*h)
qm=0.2;
q_Smax=0.6;
Yas=0.5; //g/g
Yoa=0.5;
Yxa=0.5;
Yem=0.5;
Yos=1.5;
Yxsof=0.22;
H=1000;

y0=[Kap;Ksa;Ko;Ks;Kia;Kis;p_Amax;q_Amax;qm;q_Smax;Yas;Yoa;Yxa;Yem;Yos;Yxsof;H];

yinf=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100];

ysup=[%inf,%inf,%inf,%inf,%inf,%inf,3,3,3,3,3,3,3,3,3,3,10000];

[fopt,xopt,gopt]=leastsq(Differences,'b',yinf,ysup,y0);

Now result is:

  0.2994018
   0.0508325
   0.0999987
   0.4994088
   0.5081272
   0.
   0.4004560
   0.7050746
   0.2774195
   0.6068328
   0.5
   0.4926150
   0.4053860
   0.5255006
   1.5018725
   0.2193901
   1000.0000

   33591.642

Running this script causes such an error:

lsoda--  caution... t (=r1) and h (=r2) are
     such that t + h = t at next step
      (h = pas). integration continues
      where r1 is :   0.5658105345269D+01   and r2 :   0.1884898700920D-17       
lsoda--  previous message precedent given i1 times
     will no more be repeated
      where i1 is :         10                                                   
lsoda--  at t (=r1), mxstep (=i1) steps   
needed before reaching tout
      where i1 is :     500000                                                   
      where r1 is :   0.5658105345270D+01                                        
Excessive work done on this call (perhaps wrong jacobian type).
at line    27 of function Differences

I understand that problem is on ODEs solving step. Thus, I have tried changing the mxstep, as also solving method type to 'adams','rk', and 'stiff' - none of this solved the problem. Using 'fix' method in ode I get this error:

ode: rksimp exit with state 3.

Please advise how to solve this?

P.S. Experimental data in file 'dane_exper_III_etap.txt':

0   30  1.4 24.1    99  6884.754
1   35  0.2 23.2    89  6959.754
2   40  0.1 21.6    80  7034.754
3   52  0.1 19.5    67  7109.754
4   61  0.1 18.7    70  7184.754
5   66  0.1 16.4    79  7259.754
6   71  0.1 15      94  7334.754
7   74  0   14.3    100 7409.754
8   76  0   13.8    100 7484.754
9   78  0   13.4    100 7559.754
9.5 79  0   13.2    100 7597.254
10  79  0   13.5    100 7634.754

Solution

  • In Scilab leastsq (based on optim) is very poor and doesn't have global convergence properties, unlike ipopt which is available as an atoms module. Install it like this:

    --> atomsInstall sci_ipopt
    

    I have modified your script in the following way

    1. keep classical use of ode for this kind of biological kinetics, i.e. "stiff" (which uses BDF method). The Runge-Kutta you were using is very poor as it is an explicit method only for gentle ODEs.
    2. use ipopt instead of leastsq
    3. use a try/catch/end block around the computation of the residual in order to catch failing calls to the ode solver.
    4. use some weight for the residual. You should play with it in order to improve the fit
    5. use a strictly positive lower bound instead of 0, as very low value of some parameters make the ode solver fail.
    6. add a drawing callback that also saves current value of parameters in case where you stop the optimization with ctrl-C.
    //RHSs of ODEs to be fitted:
    
    function dx=model3(t,x,Kap,Ksa,Ko,Ks,Kia,Kis,p_Amax,q_Amax,qm,q_Smax,Yas,Yoa,Yxa,Yem,Yos,Yxsof,H)
        X=x(1);
        S=x(2);
        A=x(3);
        DO=x(4);
        V=x(5);
        
        qs=((q_Smax*S/(S+Ks))*Kia/(Kia+A));
        qsof=(p_Amax*qs/(qs+Kap));
        qsox=(qs-qsof)*DO/(DO+Ko);
        qsa=(q_Amax*A/(A+Ksa))*(Kis/(qs+Kis));
        pa=qsof*Yas;
        qa=pa-qsa;
        qo=(qsox-qm)*Yos+qsa*Yoa;
        u=(qsox-qm)*Yem+qsof*Yxsof+qsa*Yxa;
        
        dx(1)=u*X-F*X/V;
        dx(2)=(F*(Sf-S)/V)-qs*X;
        dx(3)=qsa*X-(F*A/V);
        dx(4)=200*(100-DO)-qo*X*H;
        dx(5)=F;
    endfunction
    
    
    //calculating differences between calculated values and experimental data:
    
    function [f,x_calc]=Differences(k, t, x_exp)
        Kap=k(1); //g/L
        Ksa=k(2); //g/L
        Ko=k(3); //g/L
        Ks=k(4); //g/L
        Kia=k(5); //g/L
        Kis=k(6); //g/L
        p_Amax=k(7); //g/(g*h)
        q_Amax=k(8); //g/(g*h)
        qm=k(9);
        q_Smax=k(10);
        Yas=k(11); //g/g
        Yoa=k(12);
        Yxa=k(13);
        Yem=k(14);
        Yos=k(15);
        Yxsof=k(16);
        H=k(17);
        x0=x_exp(1,:);
        t0=0;
        F=75;
        Sf=500;
        [x_calc]=ode("stiff",x0',t0,t,list(model3,Kap,Ksa,Ko,Ks,Kia,Kis,p_Amax,q_Amax,qm,q_Smax,Yas,Yoa,Yxa,Yem,Yos,Yxsof,H));
        diffmat=(x_calc'-x_exp)*residual_weight;
        //column vector of differences (concatenates 4 columns of the difference matrix)
        f=diffmat(:);
        MYDATA.funeval=MYDATA.funeval+1;
    endfunction
    
    function [f,g]=normdiff2(k,new_k,t,x_exp)
        try
            res = Differences(k,t,x_exp)
            if  argn(1) == 2
                JacRes = numderivative(list(Differences,t,x_exp),k)
                g = 2*JacRes'*res;
            end
            f = sum(res.*res)
        catch
            f=%inf;
            g=%inf*ones(k);
        end
    endfunction
    
    function out=callback(param)
        global MYDATA
        if isfield(param,"x")
            k = param.x;
            MYDATA.k = k;
            [f,x_calc]=Differences(k,t,x_exp)
            plot_weight = diag(1./max(x_exp,'r'));
            drawlater
            clf
            plot(t,x_exp*plot_weight,'-o')
            plot(t,x_calc'*plot_weight,'-x')
            legend X S A DO X
            drawnow
        end
        out = %t;
    endfunction
    
    //experimental data:
    //Dat=fscanfMat('dane_exper_III_etap.txt');
    
    Dat = [
    0   30  1.4 24.1    99  6884.754
    1   35  0.2 23.2    89  6959.754
    2   40  0.1 21.6    80  7034.754
    3   52  0.1 19.5    67  7109.754
    4   61  0.1 18.7    70  7184.754
    5   66  0.1 16.4    79  7259.754
    6   71  0.1 15      94  7334.754
    7   74  0   14.3    100 7409.754
    8   76  0   13.8    100 7484.754
    9   78  0   13.4    100 7559.754
    9.5 79  0   13.2    100 7597.254
    10  79  0   13.5    100 7634.754]
    
    t=Dat(:,1);
    x_exp(:,1)=Dat(:,2);
    x_exp(:,2)=Dat(:,3);
    x_exp(:,3)=Dat(:,4);
    x_exp(:,4)=Dat(:,5);
    x_exp(:,5)=Dat(:,6);
    
    global MYDATA;
    MYDATA.funeval=0;
    
    // Initial guess
    Kap=0.3; //g/L
    Ksa=0.05; //g/L
    Ko=0.1; //g/L
    Ks=0.5; //g/L
    Kia=0.5; //g/L
    Kis=0.05; //g/L
    p_Amax=0.4; //g/(g*h)
    q_Amax=0.8; //g/(g*h)
    qm=0.2;
    q_Smax=0.6;
    Yas=0.5; //g/g
    Yoa=0.5;
    Yxa=0.5;
    Yem=0.5;
    Yos=1.5;
    Yxsof=0.22;
    H=100;
    
    k0 = [Kap;Ksa;Ko;Ks;Kia;Kis;p_Amax;q_Amax;qm;q_Smax;Yas;Yoa;Yxa;Yem;Yos;Yxsof;H];
    
    residual_weight = diag(1./[79,1.4, 24.1, 100, 7634.754]);
    
    BIG = 1000;
    SMALL = 1e-3;
    problem = struct();
    problem.f = list(normdiff2,t,x_exp);
    problem.x0 = k0;
    problem.x_lower = [SMALL*ones(16,1);100];
    problem.x_upper = [BIG,BIG,BIG,BIG,BIG,BIG,3,3,3,3,3,3,3,3,3,3,10000]';
    problem.int_cb = callback;
    problem.params = struct("max_iter",200);
    //
    k = ipopt(problem)
    

    Here is a plot of the results after 100 iterations (you can change the value in the ipopt options). However, don't expect normal termination as

    1. it is almost sure that your parameter set is not identifiable
    2. finite difference gradient with ODEs is very innacurate.

    Hope it will help you a little bit... enter image description here