
/*----------------------------------------------------------------------
  This is the Michaelis/Menten model function, for use by 3dNLfim.
  Implemented for Jasmin Salloum and Vijay Ramchandani.

  The differential equations are:

      cp'(t) = rate*0.8/v  - vmax*cp(t)/(km+cp(t))
                           - k12*cp(t) + k21*ct(t)
      ct'(t) = k12 * cp(t) - k21 * ct(t)

  They will be solved using the Modified Euler's method:

      c(t+d) = c(t) + d/2 * (c'(t) + c'(t+d))

  The result is a CP time series.

  Author: Rick Reynolds  19 Jan 2006
 *----------------------------------------------------------------------
*/

#include "NLfit_model.h"
#define  NL_MM_DEF_DT   0.1    /* default dt, in seconds */

void signal_model 
(
    float  * params,           /* parameters for signal model */
    int      ts_len,           /* length of time series data */
    float ** x_array,          /* independent variable matrix */
    float  * ts_array          /* estimated signal model time series */  
);

/* computation of time series */
int compute_ts( float * rtimes, float * rates, int nrates,
                float * ts_cp, int ts_len,
                float dt, float ** ts_times,
                float v, float vmax, float k12, float k21, float mag, float km);
int get_init_data( float ** rtime, float ** rates, int * len, float * dt );

static int debug = 0;
/*----------------------------------------------------------------------

  Initialize MODEL_interface struct with default values and model name.
*/

DEFINE_MODEL_PROTOTYPE  /* for C++ */

MODEL_interface * initialize_model ()
{
    MODEL_interface * M;

    /* get space for new struct */
    M = (MODEL_interface *)malloc(sizeof(MODEL_interface));

    strcpy(M->label, "michaelis_menton");  /* Michaelis/Menten model    */
    M->model_type = MODEL_SIGNAL_TYPE;     /* signal model (not noise)  */
    M->params = 5;                         /* number of user parameters */

    /* set param labels */
    strcpy(M->plabel[0], "v");
    strcpy(M->plabel[1], "vmax");
    strcpy(M->plabel[2], "k12");
    strcpy(M->plabel[3], "k21");
    strcpy(M->plabel[4], "mag");

    /* min and max constraints will default to the expected mean +/- 20% */
    M->min_constr[0] = 120.000;  M->max_constr[0] = 180.000;  /* mean 150  */
    M->min_constr[1] =   0.880;  M->max_constr[1] =   1.320;  /* mean 1.1  */
    M->min_constr[2] =   0.056;  M->max_constr[2] =   0.084;  /* mean 0.07 */
    M->min_constr[3] =   0.040;  M->max_constr[3] =   0.060;  /* mean 0.05 */
    M->min_constr[4] =   0.080;  M->max_constr[4] =   0.120;  /* mean 0.1  */
  
    /* set the signal model generator callback */
    M->call_func = (void_func *)&signal_model;

    return (M);
}


/*----------------------------------------------------------------------
  Compute the time series resulting from the differential equations
  constrained by the 4 parameters : v, vmax, k12, k21.

  On the first call:
        - get the rate time series data via an NLfim aux file
        - check for an aux param, dt, the time step for the diff eq soln.
*/
void signal_model (
    float  * params,           /* parameters for signal model */
    int      ts_len,           /* length of time series data */
    float ** x_array,          /* independent variable matrix */
    float  * ts_array          /* estimated signal model time series */  
)
{
    static int     first_call = 1;
    static int     rlen;              /* length of infusion data  */
    static float * rtime, * rates;    /* infusion times and rates */
    static float   dt;                /* computational time delta */
    float          TR, km = 15.0;     /* fixed                    */

    /* first time here, get rate info, along with dt */
    if( first_call )
    {
        if( get_init_data( &rtime, &rates, &rlen, &dt ) != 0 )
            exit(1); /* bad things, man, bad things */
        if( debug )
            fprintf(stderr,"+d init params (v, vmax, k12, k21, mag)\n"
                           "             = (%f, %f, %f, %f, %f)\n",
                    params[0], params[1], params[2], params[3], params[4]);
        if( debug > 2 )
        {
            int c;
            fprintf(stderr,"+d computation times (%d events): \n", ts_len);
            for( c = 0; c < ts_len; c++ )
                fprintf(stderr,"  %.1f", x_array[c][1]);
            fputc('\n', stderr);
        }
        first_call = 0;
    }

    TR = x_array[1][1] - x_array[0][1];  /* assume time delta is constant */
    if( dt > TR )
    {
        fprintf(stderr,"** dt > TR (%f > %f), this is unacceptable\n"
                       "   refusing to go on with life...\n", dt, TR);
        exit(1);
    }

    compute_ts( rtime, rates, rlen, ts_array, ts_len, dt, x_array,
                params[0], params[1], params[2], params[3], params[4], km );
}


/*----------------------------------------------------------------------
 * compute a time series (from time/rate), starting from 0 
 *             -- assume a constant rate, until it changes
 *
 * use the modified euler's method: c(t+d) = c(t) + d/2 * (c'(t) + c'(t+d))
 *
 * where    cp'(t) = rate*0.8/v - vmax*cp(t)/(km+cp(t))
 *                              - k12*cp(t) + k21*ct(t)
 * and      ct'(t) = k12 * cp(t) - k21 * ct(t)
 *
 * note that computation assumes that at the first time step after the
 *     current rate time, the subsequent rate is used - i.e. we do not wait
 *     until the time of the next rate to start applying it
*/
int compute_ts( float * rtimes, float * rates, int nrates,
                float * ts_cp, int ts_len, /* result and length */
                float dt, float ** ts_times, /* times are in column 1 */
                float v, float vmax, float k12, float k21, float mag, float km)
{
    /* computation variables */
    double ct = 0.0, cp = 0.0;      /* cur values       */
    double ct0, cp0, ct1, cp1;      /* old values       */
    double dct, dcp;                /* derivatives      */

    double cur_time;                /* current time, in seconds      */
    double rate_time, rate;         /* time and rate from list       */
    double dtm;                     /* dt converted to minutes       */
    double tr_time;                 /* for output time, if tr>0      */
    int    irate, itr;              /* index into TR and rates list  */

       
    dtm       = dt / 60.0;    /* diff. eq. has time in minutes */
    cur_time  = 0.0;          /* current time, in seconds      */
    tr_time   = 0.0;          /* time at current tr            */
    rate_time = 0.0;          /* finish time for current rate  */
    rate      = 0.0;          /* infusion rate                 */
    irate     = 0;            /* angry index into rate list    */

    for( itr = 0; itr < ts_len; itr++ ) /* loop over result time indices   */
    {
        tr_time = ts_times[itr][1];

        while( cur_time <= tr_time )    /* repeat until next result time   */
        {
            cp0 = cp;                   /* set old values and get new ones */
            ct0 = ct;
            dcp = rate*0.8/v - vmax*cp0/(km+cp0) - k12*cp0 + k21*ct0;
            dct = k12 * cp0 - k21 * ct0;
            cp1 = cp0 + dtm * dcp;    /* approximation for new cp */
            ct1 = ct0 + dtm * dct;    /* approximation for new ct */
            cp  = cp0 + 0.5 * dtm *   /* ave of old and approx new slopes */
                  (dcp + rate*0.8/v - vmax*cp1/(km+cp1) - k12*cp1 + k21*ct1);
            ct  = ct0 + 0.5*dtm*(dct + k12 * cp1 - k21 * ct1);

            cur_time += dt;

            /* if more rates to apply && it is time to apply the next */
            if( irate < nrates && cur_time > rate_time )
            {
                rate = rates[irate];
                rate_time = rtimes[irate] * 60; /* minutes to seconds */
                irate++;
            }
        }

        ts_cp[itr] = cp * mag;
    }

    return 0;
}


/* get and read rate file, and get computational time delta */
int get_init_data( float ** rtime, float ** rates, int * len, float * dt )
{
    MRI_IMAGE * im;
    char      * rate_file;
    char      * dt_text;

    if( !rtime || !rates || !len || !dt )
    {
        fprintf(stderr,"** get_init_data: bad params %p,%p,%p,%p\n",
                rtime, rates, len, dt);
        return 1;
    }

    /* get rate file name, and then try to read it */
    rate_file = my_getenv("AFNI_MM_MODEL_RATE_FILE");
    if( !rate_file )
    {
        fprintf(stderr,"\n** NLfim: need env var AFNI_MM_MODEL_RATE_FILE\n");
        fprintf(stderr,"   (might also want AFNI_MM_MODEL_DT)\n");
        return 1;
    }

    im = mri_read_1D(rate_file);
    if( !im )
    {
        fprintf(stderr,"** failed to open rate file %s\n",rate_file);
        return 1;
    }

    /* set the pointers and let the image dangle...  rates is second row */
    /* (should add cleanup function to MODEL_interface...)               */
    *rtime = MRI_FLOAT_PTR(im);
    *rates = *rtime + im->nx;
    *len   = im->nx;

    /* check to see if the rate times are in seconds */
    dt_text = my_getenv("AFNI_MM_MODEL_RATE_IN_SECS");
    if( dt_text && (*dt_text == 'y' || *dt_text == 'Y') )
    {
        int c;
        fprintf(stderr,"NLfim: rate times are taken in seconds\n");
        /* so convert to minutes */
        for( c = 0; c < *len; c++ ) (*rtime)[c] /= 60.0;
    }

    /* get dt from another env var */
    dt_text = my_getenv("AFNI_MM_MODEL_DT");
    if( dt_text )
        *dt = atof(dt_text);
    else  /* if user did not provide it... */
    {
        fprintf(stderr,"NLfim: MM: using default dt of %.3f s\n", NL_MM_DEF_DT);
        fprintf(stderr,"       (use env var AFNI_MM_MODEL_DT to override)\n");
        *dt = NL_MM_DEF_DT;
    }

    /* get debug level (abuse now unneeded dt_text variable) */
    dt_text = my_getenv("AFNI_MM_MODEL_DEBUG");
    if( dt_text ) debug = atoi( dt_text );
    if( dt_text && debug )
    {
        int c;
        fprintf(stderr,"+d NLfim: debug level set to %d\n", debug);
        fprintf(stderr,"          dt = %f, rate file = %s\n", *dt, rate_file);
        if( debug > 1 )
        {
            fprintf(stderr,"    time        rate\n    --------    --------\n");
            for( c = 0; c < *len; c++ )
                fprintf(stderr, "    %8f    %8f\n", (*rtime)[c], (*rates)[c]);
        }
    }

    return 0;
}

