/* Copyright (C) 1991-2000 Simon N. Wood snw@st-and.ac.uk This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. (www.gnu.org/copyleft/gpl.html) You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /***************************************************************************/ /* This is a new dde solver, that attempts to get around the problem of */ /* interpolating lagged variables at a lower order than the integration */ /* scheme. The methods borrow heavily from: */ /* [1] C.A.H. Paul 1992 Appl.Numer.Math. 9:403-414 */ /* [2] D.J. Higham 1993 Appl.Numer.Math. 12:315-330 */ /* [3] Press et al. 1992 Numerical recipes in C C.U.P. chap. 16 */ /* The integration scheme used is an emdedded RK23 pair due to Fehlberg */ /* reported in: */ /* [4]E.Hairer, S.P.Norsett & G.Wanner 1987, Solving Ordinary differential */ /* Equations I. springer-Verlag Berlin. p170 RKF2(3)B */ /* How to derive better schemes is described in: */ /* [5] J.C. Butcher 1987 The Numerical Analysis of Ordinary Differential */ /* Equations. John Wiley & sons, Chichester. */ /* Interpolation of lagged variables is by cubic hermite polynomials. This */ /* necessitates storing values and gradients of lagged variables. Some */ /* models have to be re-cast a bit to do this, but you can always make up */ /* a lagged quantity from the lagged state variables and the gradients of */ /* the latter are obviously easy to find. [2] Shows why this effort is */ /* required. */ /* Lags of less than the timestep are dealt with by extrapolating the */ /* cubic polynomial for the last stored step, beyond that time step [1]. */ /* Switches are also dealt with. This means that lagged variables are */ /* stored twice when a switch occurs: once before and once after. However, */ /* switch tracking is not carried out, so the step length may at times */ /* reduce as switches in lagged variables are crossed, yielding derivative */ /* discontinuities. */ /* Stepping logic is from [3]. */ /* Note that the code has no scope for specifying initial histories. This */ /* could be changed, but initial history problems are seriously unpleasant.*/ /* NOTE: code not yet optimised for minimum no. of gradient evaluations */ /***************************************************************************/ #include #include #include #include "ddeq.h" #define ERRCON 1.89e-4 #define MINSTEPFUDGE 0.0 /* this number is used to set the minimum time step permissable when solving the model (it is actually a multiplier for the initial timestep passed to the integration routine). Needing to set it to a non-zero value probably indicates a problem with the continuity of the model, and will lead to inaccuracies in the model solution*/ #define MAXSTEP 100.0 /* sets the maximum permissable time step - this can be needed in order to avoid stepping right through model fetures that have a relatively short timescale. A more efficient alternative to using this is to define switches that are thrown during the `feature' (and not reset)- but this is not always possible. The maximum timestep is set to the initial timestep dt multiplied by MAXSTEP */ double dbg0,dbg1; #define SWITCHES /* The following macros are for inline calculation of cubic hermite polynomials (HERMITE) and their gradients (GHERMITE) The horrible variable names are to (hopefully) ensure that the names don't clash with anything else - find and replace with something nicer if you need to check the macro. Tested 4/10/95 (HERMITE), 20/11/95 (GHERMITE) Modified 8/1/96 - left to right evaluation sequence IS NOT standard!! */ #define HERMITE(res,x0,x1,y0,y1,g0,g1,x) \ { HeRmItE_xx0=x-x0;HeRmItE_xx1=x-x1;HeRmItE_xx12=HeRmItE_xx1*HeRmItE_xx1;\ HeRmItE_xx02=HeRmItE_xx0*HeRmItE_xx0;\ if (HeRmItE_h=x1-x0)\ res=(( (g0)*HeRmItE_xx0*HeRmItE_xx12 + (g1)*HeRmItE_xx1*HeRmItE_xx02 \ +((y0)*(2.0*HeRmItE_xx0+(HeRmItE_h))*HeRmItE_xx12- \ (y1)*(2.0*HeRmItE_xx1-HeRmItE_h)*HeRmItE_xx02)/HeRmItE_h)/ \ (HeRmItE_h*HeRmItE_h)); else res=y0;} double HeRmItE_h,HeRmItE_xx1,HeRmItE_xx12,HeRmItE_xx02,HeRmItE_xx0; #define GHERMITE(res,x0,x1,y0,y1,g0,g1,x) \ { HeRmItE_xx0=x-x0;HeRmItE_xx1=x-x1;HeRmItE_xx12=HeRmItE_xx1*HeRmItE_xx1;\ HeRmItE_xx02=HeRmItE_xx0*HeRmItE_xx0;\ if (HeRmItE_h=x1-x0)\ res=(( (g0)*(HeRmItE_xx12+2.0*HeRmItE_xx0*HeRmItE_xx1) \ + (g1)*(HeRmItE_xx02+2.0*HeRmItE_xx0*HeRmItE_xx1) \ + ((y0)*2.0*HeRmItE_xx1*(2.0*HeRmItE_xx0+HeRmItE_h + HeRmItE_xx1) \ - (y1)*2.0*HeRmItE_xx0*(2.0*HeRmItE_xx1-HeRmItE_h + HeRmItE_xx0))/HeRmItE_h )\ / (HeRmItE_h*HeRmItE_h) ); else res=g0;} /***************** end of definitions for hermite macros *******************/ void ErrorMessage(char *msg,int fatal); /***************************************************************************/ /* Global variables */ /***************************************************************************/ void (*poutput)(double *,double, void *, int)=output; double (*ppastvalue)(int,double,int)=realpastvalue, (*ppastgradient)(int,double,int)=realpastgradient; histype history; long accepted=0L,rejected=0L; /***************************************************************************/ /* Routines that are not problem specific */ /***************************************************************************/ //int is_model_discrete() // indicates to DDEfit that a full continuous model is being used //{ return(0); //} void rk23(state,newstate,g,newg,error,coeff,ns,time,dt) double *state,*newstate,*g,*newg,*error,*coeff,time,dt;int ns; /* Takes a single integration step from time to time+dt using a 3rd order embedded Runge-Kutta Fehlberg method: E.Hairer, S.P.Norsett & G.Wanner 1987, Solving Ordinary differential Equations I. springer-Verlag Berlin. p170 RKF2(3)B The routine returns an estimated error vector for adaptive timestepping. The gradient of the state variables is to be given in function grad(). The routine uses the lower order scheme for updating, fortunately Fehlberg optimised the coefficients for the lower order scheme..... 4/10/95. NOTE: not yet optimised for minimum gradient evaluations - see original table of coeffs. Partially optimised 9/10/95 Only valid for ci=b4i! Takes gradient at time in g, puts gradient at time+dt in newg - these can be the same pointer/array */ { static int first=1,oldns=-1; static double *k1,*k2,*k3,*k4, bct1,bct2,bct3, /* variables to save 3*ns multiplications */ /* Embedded RKF table - coded this way to save addressing time */ a2=0.25, a3=27.0/40.0, b21= 0.25, b31=-189.0/800.0, b32= 729.0/800.0, b41= 214.0/891.0, b42= 1.0/33.0, b43= 650.0/891.0, /* c1= 214.0/891.0, c2= 1.0/33.0, c3= 650.0/891.0,*/ cc1= 533.0/2106.0, cc3= 800.0/1053.0, cc4=-1.0/78.0; int i; if ((first)||(oldns!=ns)) { if (!first) { free(k2);free(k3);free(k4);} oldns=ns;first=0; if (ns>0) { k2=(double *)calloc((size_t)ns,sizeof(double)); k3=(double *)calloc((size_t)ns,sizeof(double)); k4=(double *)calloc((size_t)ns,sizeof(double)); } } k1=g; bct1=b21*dt; for (i=0;i=size)||(k1<0L)) k1=0L; while ((x[k1]t)&&(k!=offsetplus)) { if (k==0L) k=size-1L; else k--;} k1=k+1L;if (k1==size) k1=0L; if (tx[k1])&&(x[k]==x[k1])) /* then its extrapolation through a switch */ res=g[k1]; /* so use linear extrapolation just this once 20/11/95*/ else #endif if (history.fast) { t-=x0; if (!cdset[k]) /* set coeffs for rapid calculation */ { y0=y[k]; h=1.0/(x1-x0);y1y0=y[k1]-y0; g+=k; g0= *g; g++; g1= *g;c+=k;d+=k; *c = h*(3*h*y1y0-2*g0-g1); *d =h*h*(g0+g1-2*h*y1y0); cdset[k]=1; res= g0 + 2*t*( *c + 3* *d*t); } else res=g[k] + 2*t*(c[k] + 3* d[k]*t); } else GHERMITE(res,x0,x1,y[k],y[k1],g[k],g[k1],t); /* 20/11/95*/ history.lagmarker[i][markno]=k; return(res); } double realpastvalue(i,t,markno) int i,markno;double t; /* Interogates the history ringbuffers. Note that there is a fair amount of assignment of one variable to another at the start: this is to save on address calculation and speed up the routine. 4/10/95*/ { long k1,k,offset,offsetplus,size; char *cdset,*estr; double res,*y,*g,*x,x0,x1,h,y1y0,*c,*d,g0,g1,y0; y=history.buff[i];g=history.gbuff[i]; if (history.fast) { c= history.cbuff[i]; d= history.dbuff[i]; cdset= history.cdset[i];} x=history.clock; /*local pointers improve address efficiency*/ offset=history.offset;size=history.size; offsetplus=offset+1L; if (offsetplus==size) offsetplus=0L; k=history.lagmarker[i][markno]; k1=k+1L;if ((k1>=size)||(k1<0L)) k1=0L; while ((x[k1]t)&&(k!=offsetplus)) { if (k==0L) k=size-1L; else k--;} k1=k+1L;if (k1==size) k1=0L; if (tx1)&&(x0==x1)) /* then its extrapolation through a switch */ res=y[k1]+(t-x1)*g[k1]; /* so use linear extrapolation just this once */ else #endif if (history.fast) { t-=x0; if (!cdset[k]) /* set coeffs for rapid calculation */ { y0=y[k];h=x1-x0; if (h>0.0) { h=1.0/h;y1y0=y[k1]-y0; g+=k; g0= *g; g++; g1= *g;c+=k;d+=k; *c = h*(3*h*y1y0-2*g0-g1); *d =h*h*(g0+g1-2*h*y1y0); } else { c+=k;d+=k;*c=0.0;*d=0.0;} cdset[k]=1; res=y0+t*(g0 + t*( *c + *d*t)); } else res=y[k] + t*(g[k] + t*(c[k] + d[k]*t)); } else HERMITE(res,x0,x1,y[k],y[k1],g[k],g[k1],t); history.lagmarker[i][markno]=k; return(res); } double zeropos(x1,x2,x3,s1,s2,s3) double x1,x2,x3,s1,s2,s3; /* finds the root in [x1,x3] of a quadratic passing through the (xi,si)s it is assumed that s3=0.0) { d=sqrt(d);a=(-b+d)/c;b=(-b-d)/c; if ((b>=-y)&&(b<=z)) a=b; else if ((a<-y)||(a>z)) ok=0; } if ((d<0.0)||(!ok)) { if (-s3fabs(d)) a=p; /* check that linear interpolation is not better */ } a+=x2; if (a>x3) a=x3; if (a<=x1) { if (a==0.0) a=udge-1.0; else if (a<0.0) a/=udge; else a*=udge;} return(a); } double istep(sw0,newsws,s0,news,g,newg,c,err,t0,t1,nsw,ns,flickedswitch) double *sw0,*newsws,*s0,*news,*g,*newg,*c,*err,t0,t1; int nsw,ns,*flickedswitch; /* executes RK23 step to next switch or target depending on which comes first If step is to the first switch then the number of that switch is returned in flickedswitch, but map() is not called. Returns how far it got. 5/10/95. g is assumed to contain the gradient at t0, it will contain the gradient at time t1 on exit. This improves efficiency by making use of info. calculated in the previous step of rk23. Note that it is necessary to try both linear and quadratic approximations, in case curvature is really zero..... */ { static int first=1,nsold,nswold,*flicked; static double *err1,*s1,*s2,*sw1,*sw2; int k,i,switches=0; double zp,dt,sp2,sp1,minp,ds,udge; if ((first)||(ns!=nsold)||(nsw!=nswold)) { if (!first) { free(err1);free(flicked);free(sw1);free(s1);free(sw2);free(s2);} first=0; sw1=(double *)calloc(nsw,sizeof(double)); sw2=(double *)calloc(nsw,sizeof(double)); s1=(double *)calloc(ns,sizeof(double)); s2=(double *)calloc(ns,sizeof(double)); err1=(double *)calloc(ns,sizeof(double)); flicked=(int *)calloc(nsw,sizeof(int)); nsold=ns;nswold=nsw; } dt=t1-t0; rk23(s0,news,g,newg,err,c,ns,t0,dt); if (nsw) switchfunctions(newsws,news,c,t1); for (i=0;i0.0)&&(newsws[i]<=0.0)) { flicked[switches]=i;switches++;} if (!switches) /* No switches so its an ordinary step */ { *flickedswitch=-1; return(t1); } /* Logic for stepping to first switch */ sp1=t0+dt*0.5; for (k=0;k<200;k++) /* if k gets to 100 routine fails */ { rk23(s0,s1,g,newg,err,c,ns,t0,sp1-t0); /* step to approx. 1st switch position */ switchfunctions(sw1,s1,c,sp1); switches=0; for (i=0;i0.0)&&(sw1[i]<=0.0)) { flicked[switches]=i;switches++;} if ((k)&&(switches==1)) /* MACRO after debug */ { *flickedswitch=flicked[0]; for (i=0;i0.0)&&(sw2[i]<=0.0)) { flicked[switches]=i;switches++;} if (!switches) /* MACRO after debug */ { *flickedswitch=-1; for (i=0;i0.0) /* ensuring that switch actually gets flicked, eventually */ do { sp1+=udge*ds;udge*=10.0;} while (sp1==minp); } ErrorMessage("\nProblem with switch logic\n",1);return(-1.0); } void dde(s,c,t0,t1,dt,eps,dout,ns,nsw,nhv,hbsize,nlag,reset,step) double *s, /* State variables */ *c, /* coefficients */ t0,t1, /* start and stop times */ *dt, /* pointer to initial timestep (returns final step - which is step that would have been used if t1 not reached!) when reset=1 this is used to set the maximum and minimum steps using macro definitions at head of file.*/ eps, /* fractional tolerance for adaptive stepping */ dout; /* interval for output via user routine output(). Every time a step passes 1 or more times of the form t0+i*dout output() is called once. Hence output is only roughly regular. dout=0.0 for no output. */ long hbsize; /* The number of elements to store in the history buffer */ int nsw, /* number of switches */ ns, /* number of state variables */ nhv, /* number of lagged variables */ reset, /* set to 0 not to reset, to 1 to reset */ nlag, /* number of place markers per history variable */ step; /* 0 - adaptive stepping; 1 - record adaptive; 2 - playback adaptive; 3 - free record memory and use adaptive; 4 - fixed step */ /* Note that the routine can store and replay a series of steps (the integration mesh used). To do this call the routine with step=1. The mesh will be stored for as long as step=1. When replaying, always call with the same set of start and end times (in the same sequence) used for recording */ { double D,Da,errmax,rerr,target,t,ti, *err,*newsws,*sws,*news,*newg,*dum,*sp,*nswp,*swp,*nsp,*e0,*scale; static double *g,mindt,maxdt,**recdt, pshrink = -0.333333333333333, pgrow = -0.333333333333333, safety = 0.9, errcon; static long no_dt=0L,max_no_dt=0L,dt_off=0L,dt_arr=0L,rp_arr,rp_off; /* storing information on mesh recording memory */ static int first=1; static char *errs; long i,iout=1L; int swi,adaptive=0; if (first) errcon=pow(5.0/safety,1.0/pgrow); nswp=newsws=(double *)calloc((size_t)nsw,sizeof(double)); swp=sws=(double *)calloc((size_t)nsw,sizeof(double)); nsp=news=(double *)calloc((size_t)ns,sizeof(double)); newg=(double *)calloc((size_t)ns,sizeof(double)); err=(double *)calloc((size_t)ns,sizeof(double)); e0=(double *)calloc((size_t)ns,sizeof(double)); scale=(double *)calloc((size_t)ns,sizeof(double)); statescale(scale); if (nsw) switchfunctions(sws,s,c,t0); if ((step==1)&&(no_dt==0L)) /* initialising memory for recording mesh */ { recdt=(double **)calloc(1000,sizeof(double *));max_no_dt=1000L; recdt[0]=(double *)calloc(1000,sizeof(double));no_dt=1L; } if ((step==3)&&(no_dt>0L)) /* free memory for recording mesh */ { for (i=0;i0.1*fabs(s[i])&&(s[i]!=0.0)) (*dt)=0.1*fabs(s[i])/fabs(g[i]); if (step==1) { for (i=dt_arr+1;it1) { target=t1;} t=istep(sws,newsws,s,news,g,newg,c,err,t0,target,nsw,ns,&swi); errmax=0.0; if ((adaptive)&&(D>mindt)) { /* calculate the maximum tolerable errors for each variable */ for (i=0;i-1e-150) rerr=0.0;else rerr=err[i]/e0[i]; rerr=fabs(rerr); if (rerr>errmax) errmax=rerr; } } if (errmax<1.0) /* then all errors were less than their permitted maximum */ { accepted++; if (step==1) /* record steps */ { if (dt_off==1000L) { dt_off=0L;dt_arr++; if (dt_arr==no_dt) /* have to allocate some more memory */ { if (no_dt==max_no_dt) /* need to expand memory table */ { max_no_dt+=1000L; recdt=(double **)realloc(recdt,(size_t)(sizeof(double *)*max_no_dt)); } recdt[dt_arr]=(double *)calloc(1000,sizeof(double)); no_dt++; } } recdt[dt_arr][dt_off]=t;dt_off++; } dum=s;s=news;news=dum;dum=sws;sws=newsws;newsws=dum; dum=g;g=newg;newg=dum; updatehistory(g,s,c,t); if (dout) /* outputting results */ if ( t > ti+iout*dout ) { poutput(s,t,(void *)NULL,0); while (ti+iout*dout<=t) iout++; } t0=t; if ( swi > -1 ) { if (dout) poutput(s,t,(void *)NULL,0); map(s,c,t,swi); if (dout) poutput(s,t,(void *)NULL,0); grad(g,s,c,t);updatehistory(g,s,c,t); } else /* increase stepsize */ { if ((adaptive)&&(t errcon ? safety*D*pow(errmax,pgrow) : 5.0*D); if (D>maxdt) D=maxdt; /* for (i=0;ifabs(s[i])) D=fabs(s[i])/fabs(g[i]);*/ } } else /* the error was too large and the step must be scaled */ { if (errmax>100.0) errmax=100.0; Da=t-t0; /* Step actually achieved */ rejected++; /* Shrink D from Da */ #ifndef STEPOFF D=safety*Da*pow(errmax,pshrink); D=( D < 0.1*Da ? 0.1*Da : D ); if (D < 1e-16*(*dt)) { errs=(char *)calloc(200,sizeof(char)); sprintf(errs,"Stepsize reduced to %g at time %g.",D,t); ErrorMessage(errs,0); } #endif t=t0; } } (*dt)=D; if (dout) poutput(s,t1,(void *)NULL,0); for (i=0;i