/*************************************************************************\ * Copyright (c) 2002 The University of Chicago, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * This file is distributed subject to a Software License Agreement found * in the file LICENSE that is included with this distribution. \*************************************************************************/ /* file: rk_ode.c * contents: rk_odeint(), rk_qcstep() rk4_step() * rk_odeint1(), rk_odeint2(), rk_odeint3(), rk_odeint4() * rk_odeint_na() * * purpose: fourth-order Runge-Kutta ODE integration routines. * double-precision version * * Michael Borland, 1989. $Log: rkODE.c,v $ Revision 1.7 2009/06/21 18:13:34 borland Updated rk_odein3_na() to include stochastic process between steps. Revision 1.6 2003/08/28 19:56:28 soliday Cleaned up some of the code. Revision 1.5 2002/08/14 16:18:59 soliday Added Open License Revision 1.4 1999/07/29 21:26:39 borland Improved exit function iteration. Revision 1.3 1999/05/28 14:59:33 soliday Removed compiler warnings under Linux. Revision 1.2 1995/09/05 21:20:46 saunders First test release of the SDDS1.5 package. */ #include "mdb.h" #include #define MAX_EXIT_ITERATIONS 400 #define ITER_FACTOR 0.995 #define TINY 1.0e-30 void new_scale_factors_dp(double *yscale, double *y0, double *dydx0, double h_start, double *tiny, long *accmode, double *accuracy, long n_eq); void initial_scale_factors_dp(double *yscale, double *y0, double *dydx0, double h_start, double *tiny, long *accmode, double *accuracy, double *accur, double x0, double xf, long n_eq); void rk4_step(double *yf, double x, double *yi, double *dydx, double h, long n_eq, void (*derivs)(double *dydx, double *y, double x)); long rk_qcstep(double *yf, double *x, double *yi, double *dydx, double htry, double *hdid, double *hnext, double *yscale, long n_eq, void (*derivs)(double *dydx, double *y, double x), long *misses); void report_state_dp(FILE *fp, double *y, double *dydx, double *yscale, long *misses, double x, double h, long n_eq); /* routine: rk_odeint() * purpose: integrate a set of ordinary differential equations until * the upper limit of the independent variable is reached, or * until a user-supplied function is zero * returns: 0<= on failure, >=1 on success */ long rk_odeint( double *y0, /* initial/final values of dependent variables */ /* (*derivs)(dydx, y, x): */ void (*derivs)(double *dydx, double *y, double x), long n_eq, /* number of equations */ /* for each dependent variable: */ double *accuracy, /* desired accuracy--see below for meaning */ long *accmode, /* desired accuracy-control mode */ double *tiny, /* small value relative to what's important */ long *misses, /* number of times each variable caused reset of step size */ /* for the dependent variable: */ double *x0, /* initial/final value */ double xf, /* upper limit of integration */ double x_accuracy, /* accuracy of final value */ double h_start, /* suggested starting step size */ double h_max, /* maximum step size allowed */ double *h_rec, /* recommended step size for continuation */ /* function for determining when to stop integration: */ double (*exit_func)(double *dydx, double *y, double x), double exit_accuracy, /* how close to zero to get */ long n_to_skip, /* number of zeros of exit function to skip before returning */ /* function to store points: */ void (*store_data)(double *dydx, double *y, double x, double exval) ) { double *y_return; double *dydx0, *y1, *dydx1, *dydx2, *y2; double ex0, ex1, ex2, *yscale, *accur; double h_used, h_next, x1, x2, xdiff; long i, n_exit_iterations, n_step_ups=0, is_zero; #define MAX_N_STEP_UPS 10 if (*x0>xf) return(DIFFEQ_XI_GT_XF); if (FABS(*x0-xf) no accuracy control * accmode = 0 -> accuracy[i] is desired fractional accuracy at * each step for ith variable. tiny[i] is lower limit * of significance for the ith variable. * accmode = 1 -> same as accmode=0, except that the accuracy is to be * satisfied globally, not locally. * accmode = 2 -> accuracy[i] is the desired absolute accuracy per * step for the ith variable. tiny[i] is ignored. * accmode = 3 -> samed as accmode=2, except that the accuracy is to * be satisfied globally, not locally. */ for (i=0; i3) bomb("accmode must be on [-1, 3] (rk_odeint)", NULL); if (accmode[i]<2 && tiny[i] MAX_N_STEP_UPS) bomb("cannot take initial step (rk_odeint--1)", NULL); h_start = (n_step_ups-1?h_start*10:h_used*10); continue; } /* calculate derivatives and exit function at new point */ (*derivs)(dydx1, y1, x1); ex1 = exit_func?(*exit_func)(dydx1, y1, x1):0; if (store_data) (*store_data)(dydx1, y1, x1, ex1); /* check for change in sign of exit function */ if (exit_func && SIGN(ex0)!=SIGN(ex1) && !is_zero) { if (n_to_skip==0) break; else { --n_to_skip; is_zero = 1; } } /* check for end of interval */ if (FABS(xdiff=xf-x1)h_max?(h_max?h_max:h_next):h_next); /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); } while (1); *h_rec = h_start; if (!exit_func) { printf("failure in rk_odeint(): solution stepped outside interval\n"); tfree(dydx0); tfree(dydx1); tfree(dydx2); tfree(yscale); tfree(accur); if (y0!=y_return) tfree(y0); if (y1!=y_return) tfree(y1); if (y2!=y_return) tfree(y2); return(DIFFEQ_OUTSIDE_INTERVAL); } /* The root has been bracketed. */ n_exit_iterations = MAX_EXIT_ITERATIONS; do { /* try to take a step to the position where the zero is expected */ h_start = -ex0*(x1-*x0)/(ex1-ex0)*ITER_FACTOR; x2 = *x0; /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); if (!rk_qcstep(y2, &x2, y0, dydx0, h_start, &h_used, &h_next, yscale, n_eq, derivs, misses)) bomb("step size too small (rk_odeint--2)", NULL); /* check the exit function at the new position */ (*derivs)(dydx2, y2, x2); ex2 = (*exit_func)(dydx2, y2, x2); if (FABS(ex2)xf) return(DIFFEQ_XI_GT_XF); if (FABS(*x0-xf) no accuracy control * accmode = 0 -> accuracy[i] is desired fractional accuracy at * each step for ith variable. tiny[i] is lower limit * of significance for the ith variable. * accmode = 1 -> same as accmode=0, except that the accuracy is to be * satisfied globally, not locally. * accmode = 2 -> accuracy[i] is the desired absolute accuracy per * step for the ith variable. tiny[i] is ignored. * accmode = 3 -> samed as accmode=2, except that the accuracy is to * be satisfied globally, not locally. */ for (i=0; i3) bomb("accmode must be on [-1, 3] (rk_odeint)", NULL); if (accmode[i]<2 && tiny[i] MAX_N_STEP_UPS) { puts("error: cannot take step (rk_odeint1--1)"); printf("xf = %.16e x0 = %.16e\n", xf, *x0); printf("h_start = %.16e h_used = %.16e\n", h_start, h_used); puts("dump of integration state:"); puts(" variable value derivative scale misses"); puts("---------------------------------------------------------------"); for (i=0; ih_max?(h_max?h_max:h_next):h_next); /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); } while (1); } /* routine: rk_odeint2() * purpose: integrate a set of ordinary differential equations until * the upper limit of the independent variable is reached or * until a specified component of the solution reaches a * specified within a specified accuracy * note: this is the same as rk_odeint(), but doesn't look for the zero * of a function of the solution, or provide intermediate output. * It integrates until a specified component of the solution reaches * a specified value. * It is about 20% faster than rk_odeint() called with NULL's for * exit_func and store_data. * * returns: 0<= on failure, 1 on success */ long rk_odeint2( double *y0, /* initial/final values of dependent variables */ /* (*derivs)(dydx, y, x): */ void (*derivs)(double *dydx, double *y, double x), long n_eq, /* number of equations */ /* for each dependent variable: */ double *accuracy, /* desired accuracy--see below for meaning */ long *accmode, /* desired accuracy-control mode */ double *tiny, /* small value relative to what's important */ long *misses, /* number of times each variable caused reset of step size */ /* for the dependent variable: */ double *x0, /* initial/final value */ double xf, /* upper limit of integration */ double x_accuracy, /* accuracy of final value */ double h_start, /* suggested starting step size */ double h_max, /* maximum step size allowed */ double *h_rec, /* recommended step size for continuation */ /* for determining when to stop integration: */ double exit_value, /* value to be obtained */ long i_exit_value, /* index of independent variable this pertains to */ double exit_accuracy, /* how close to get */ long n_to_skip /* number of zeros to skip before returning */ ) { double *y_return, *accur; double *dydx0, *y1, *dydx1, *dydx2, *y2; double ex0, ex1, ex2, x1, x2, *yscale; double h_used, h_next, xdiff; long i, n_exit_iterations, n_step_ups=0, is_zero; #define MAX_N_STEP_UPS 10 if (*x0>xf) return(DIFFEQ_XI_GT_XF); if (FABS(*x0-xf)=n_eq) bomb("index of variable for exit testing is out of range (rk_odeint2)", NULL); /* Meaning of accmode: * accmode = -1 -> no accuracy control * accmode = 0 -> accuracy[i] is desired fractional accuracy at * each step for ith variable. tiny[i] is lower limit * of significance for the ith variable. * accmode = 1 -> same as accmode=0, except that the accuracy is to be * satisfied globally, not locally. * accmode = 2 -> accuracy[i] is the desired absolute accuracy per * step for the ith variable. tiny[i] is ignored. * accmode = 3 -> samed as accmode=2, except that the accuracy is to * be satisfied globally, not locally. */ for (i=0; i3) bomb("accmode must be on [-1, 3] (rk_odeint2)", NULL); if (accmode[i]<2 && tiny[i] MAX_N_STEP_UPS) { bomb("error: cannot take initial step (rk_odeint2--1)", NULL); } h_start = (n_step_ups-1?h_start*10:h_used*10); continue; } /* calculate derivatives and exit function at new point */ (*derivs)(dydx1, y1, x1); ex1 = exit_value - y1[i_exit_value]; /* check for change in sign of exit function */ if (SIGN(ex0)!=SIGN(ex1) && !is_zero) { if (n_to_skip==0) break; else { --n_to_skip; is_zero = 1; } } /* check for end of interval */ if (FABS(xdiff=xf-x1)h_max?(h_max?h_max:h_next):h_next); /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); } while (1); *h_rec = h_start; /* The root has been bracketed. */ n_exit_iterations = MAX_EXIT_ITERATIONS; do { /* try to take a step to the position where the zero is expected */ h_start = -ex0*(x1-*x0)/(ex1-ex0)*ITER_FACTOR; x2 = *x0; /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); if (!rk_qcstep(y2, &x2, y0, dydx0, h_start, &h_used, &h_next, yscale, n_eq, derivs, misses)) bomb("step size too small (rk_odeint2--2)", NULL); /* check the exit function at the new position */ (*derivs)(dydx2, y2, x2); ex2 = exit_value - y2[i_exit_value]; if (FABS(ex2)xf) return(DIFFEQ_XI_GT_XF); if (h==0) return(DIFFEQ_ZERO_STEPSIZE); if (!(n=(xf-x)/h+0.5)) n = 1; h = (xf-x)/n; dydx0 = tmalloc(sizeof(double)*n_eq); dydx2 = tmalloc(sizeof(double)*n_eq); dydx1 = tmalloc(sizeof(double)*n_eq); ytemp = tmalloc(sizeof(double)*n_eq); n_eq--; /* for convenience in loops */ h6 = h/6; hh = h/2; for (j=0; j=0; i--) ytemp[i] = y0[i] + hh*dydx0[i]; /* second step */ (*derivs)(dydx1, ytemp, xh); for (i=n_eq; i>=0; i--) ytemp[i] = y0[i] + hh*dydx1[i]; /* third step */ (*derivs)(dydx2, ytemp, xh); for (i=n_eq; i>=0; i--) { ytemp[i] = y0[i] + h*dydx2[i]; dydx2[i] += dydx1[i]; } /* fourth step */ (*derivs)(dydx1, ytemp, x=xh+hh); for (i=n_eq; i>=0; i--) y0[i] += h6*(dydx0[i] + dydx1[i] + 2*dydx2[i]); } tfree(dydx0); tfree(dydx2); tfree(dydx1); tfree(ytemp); *x0 = x; return(DIFFEQ_END_OF_INTERVAL); } /* routine: rk_odeint3() * purpose: integrate a set of ordinary differential equations until * the upper limit of the independent variable is reached, or * until a user-supplied function is zero * returns: 0<= on failure, >=1 on success */ long rk_odeint3( double *yif, /* initial/final values of dependent variables */ void (*derivs)(double *dydx, double *y, double x),/* (*derivs)(dydx, y, x) */ long n_eq, /* number of equations */ /* for each dependent variable: */ double *accuracy, /* desired accuracy--see below for meaning */ long *accmode, /* desired accuracy-control mode */ double *tiny, /* small value relative to what's important */ long *misses, /* number of times each variable caused reset of step size */ /* for the dependent variable: */ double *x0, /* initial/final value */ double xf, /* upper limit of integration */ double x_accuracy, /* accuracy of final value */ double h_start, /* suggested starting step size */ double h_max, /* maximum step size allowed */ double *h_rec, /* recommended step size for continuation */ /* function for determining when to stop integration: */ double (*exit_func)(double *dydx, double *y, double x), /* function that is to be zeroed */ double exit_accuracy /* how close to zero to get */ ) { static double *y0, *yscale; static double *dydx0, *y1, *dydx1, *dydx2, *y2, *accur; static long last_neq = 0; double ex0, ex1, ex2, x1, x2; double h_used, h_next, xdiff; long i, n_exit_iterations, n_step_ups = 0; #define MAX_N_STEP_UPS 10 if (*x0>xf) return(DIFFEQ_XI_GT_XF); if (FABS(*x0-xf) no accuracy control * accmode = 0 -> accuracy[i] is desired fractional accuracy at * each step for ith variable. tiny[i] is lower limit * of significance for the ith variable. * accmode = 1 -> same as accmode=0, except that the accuracy is to be * satisfied globally, not locally. * accmode = 2 -> accuracy[i] is the desired absolute accuracy per * step for the ith variable. tiny[i] is ignored. * accmode = 3 -> samed as accmode=2, except that the accuracy is to * be satisfied globally, not locally. */ for (i=0; i3) bomb("accmode must be on [-1, 3] (rk_odeint)", NULL); if (accmode[i]<2 && tiny[i] MAX_N_STEP_UPS) bomb("error: cannot take initial step (rk_odeint3--1)", NULL); h_start = (n_step_ups-1?h_start*10:h_used*10); continue; } /* calculate derivatives and exit function at new point */ (*derivs)(dydx1, y1, x1); ex1 = (*exit_func)(dydx1, y1, x1); if (SIGN(ex0)!=SIGN(ex1)) break; /* check for end of interval */ if (FABS(xdiff=xf-x1)h_max?(h_max?h_max:h_next):h_next); /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); } while (1); *h_rec = h_start; if (!exit_func) { printf("failure in rk_odeint3(): solution stepped outside interval\n"); return(DIFFEQ_OUTSIDE_INTERVAL); } if (FABS(ex1)xf) return(DIFFEQ_XI_GT_XF); if (fabs(*x0-xf)=n_eq) bomb("index of variable for exit testing is out of range (rk_odeint4)", NULL); /* Meaning of accmode: * accmode = -1 -> no accuracy control * accmode = 0 -> accuracy[i] is desired fractional accuracy at * each step for ith variable. tiny[i] is lower limit * of significance for the ith variable. * accmode = 1 -> same as accmode=0, except that the accuracy is to be * satisfied globally, not locally. * accmode = 2 -> accuracy[i] is the desired absolute accuracy per * step for the ith variable. tiny[i] is ignored. * accmode = 3 -> samed as accmode=2, except that the accuracy is to * be satisfied globally, not locally. */ for (i=0; i3) bomb("accmode must be on [-1, 3] (rk_odeint4)", NULL); if (accmode[i]<2 && tiny[i] MAX_N_STEP_UPS) { bomb("error: cannot take initial step (rk_odeint4--1)", NULL); } h_start = (n_step_ups-1?h_start*10:h_used*10); continue; } /* calculate derivatives and exit function at new point */ (*derivs)(dydx1, y1, x1); ex1 = exit_value - y1[i_exit_value]; if (store_data) (*store_data)(dydx1, y1, x1, ex1); /* check for change in sign of exit function */ if (SIGN(ex0)!=SIGN(ex1) && !is_zero) { if (n_to_skip==0) break; else { --n_to_skip; is_zero = 1; } } /* check for end of interval */ if (fabs(xdiff=xf-x1)h_max?(h_max?h_max:h_next):h_next); /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); } while (1); *h_rec = h_start; /* The root has been bracketed. */ n_exit_iterations = MAX_EXIT_ITERATIONS; do { /* try to take a step to the position where the zero is expected */ h_start = -ex0*(x1-*x0)/(ex1-ex0)*ITER_FACTOR; x2 = *x0; /* calculate new scale factors */ new_scale_factors_dp(yscale, y0, dydx0, h_start, tiny, accmode, accur, n_eq); if (!rk_qcstep(y2, &x2, y0, dydx0, h_start, &h_used, &h_next, yscale, n_eq, derivs, misses)) bomb("step size too small (rk_odeint4--2)", NULL); /* check the exit function at the new position */ (*derivs)(dydx2, y2, x2); ex2 = exit_value - y2[i_exit_value]; if (fabs(ex2)=1 on success */ long rk_odeint3_na( double *yif, /* initial/final values of dependent variables */ void (*derivs)(double *dydx, double *y, double x),/* (*derivs)(dydx, y, x) */ long n_eq, /* number of equations */ /* for each dependent variable: */ double *accuracy, /* ignored */ long *accmode, /* ignored */ double *tiny, /* ignored */ long *misses, /* ignored */ /* for the dependent variable: */ double *x0, /* initial/final value */ double xf, /* upper limit of integration */ double x_accuracy, /* accuracy of final value */ double h_step, /* step size */ double h_max, /* ignored */ double *h_rec, /* ignored */ /* function for determining when to stop integration: */ double (*exit_func)(double *dydx, double *y, double x), /* function that is to be zeroed */ double exit_accuracy, /* how close to zero to get */ /* function for adding stochastic processes */ void (*stochastic)(double *y, double x, double h) ) { static double *y0, *yscale; static double *dydx0, *y1, *dydx1, *dydx2, *y2, *accur; static long last_neq = 0; double ex0, ex1, ex2, x1, x2; double xdiff; long i, n_exit_iterations; #define MAX_N_STEP_UPS 10 if (*x0>xf) return(DIFFEQ_XI_GT_XF); if (FABS(*x0-xf)0 && newSafetyMargin<1) safetyMargin = newSafetyMargin; if (newIncreasePower>0) increasePower = newIncreasePower; if (newDecreasePower>0) decreasePower = newDecreasePower; if (newMaxIncreaseFactor>1) maxIncreaseFactor = newMaxIncreaseFactor; } long rk_qcstep( double *yFinal, /* final values of the dependent variables */ double *x, /* initial value of independent the variable */ double *yInitial, /* initial values of the dependent variables */ double *dydxInitial, /* derivatives at x */ double hInput, /* desired step size in x */ double *hUsed, /* step size used */ double *hRecommended, /* step size recommended for next step */ double *yScale, /* allowable absolute error */ long equations, /* number of equations */ void (*derivs)(double *dydx, double *y, double x), /* function to return dy/dx at x for given y */ long *misses /* number of times each variable forces decreasing of step size. Accumulates between calls if not zeroed externally */ ) { static long last_equations = 0; static double *dydxTemp, *yTemp; double hOver2, h, xTemp; double error, maxError, hFactor; long i, iWorst=0, minStepped = 0, noAdaptation = 0; /* for speed, I avoid reallocating the arrays unless it is necessary */ if (last_equationsmaxIncreaseFactor) hFactor = maxIncreaseFactor; else if (hFactor<1) hFactor = 1; } else hFactor = 1; *hRecommended = hFactor*h; /* yTemp stores the two-step solution minus the one-step solution. An improved value is obtained by adding 1/15 this difference to the one-step solution . */ for (i=0; i=0) /* record that equation iWorst caused a step-size reduction */ misses[iWorst] += 1; /* compute a new, smaller step-size. It will be tested for underflow at the top of the loop. */ hOver2 = (h = safetyMargin*h*pow(maxError, -decreasePower))/2; } while (1); }