real phi1(real x) {return x != 0 ? expm1(x)/x : 1;}
// phi2(x)=(exp(x)-1-x)/(x^2);
// Use the identity phi2(2x)=0.25*(x*phi2(x)+1)^2+0.5*phi2(x);
real phi2(real x)
{
if(fabs(x) > 1) return (exp(x)-x-1)/(x^2);
x *= 0.125;
real x2=x*x;
real x3=x2*x;
real x5=x2*x3;
real y=Coeff[1]+x*Coeff[2]+x2*Coeff[3]+x3*Coeff[4]+x2*x2*Coeff[5]+
x5*Coeff[6]+x3*x3*Coeff[7]+x5*x2*Coeff[8]+x5*x3*Coeff[9];
y=0.25*(x*y+1.0)^2+0.5*y;
y=(x*y+0.5)^2+0.5*y;
return (2.0*x*y+0.5)^2+0.5*y;
}
// phi3(x)=(exp(x)-1-x-x^2/2)/(x^3)
// Use the identity phi3(2x)=0.125*phi2(x)*(x*phi2(x)+2)+0.25*phi3(x)
// where phi2(x)=x*phi3(x)+0.5
real phi3(real x)
{
if(fabs(x) > 1.6) return (exp(x)-0.5*x^2-x-1)/x^3;
x *= 0.125;
real x2=x*x;
real x3=x2*x;
real x5=x2*x3;
real y=Coeff[2]+x*Coeff[3]+x2*Coeff[4]+x3*Coeff[5]+
x2*x2*Coeff[6]+x5*Coeff[7]+x3*x3*Coeff[8]+x5*x2*Coeff[9]+
x5*x3*Coeff[10];
real y2=x*y+0.5;
y=0.125*y2*(x*y2+2)+0.25*y;
y2=2*x*y+0.5;
y=0.25*y2*(x*y2+1)+0.25*y;
y2=4*x*y+0.5;
return 0.25*y2*(2*x*y2+1)+0.25*y;
}
// phi4(x)=(exp(x)-1-x-x^2/2-x^3/6)/(x^4)
// Use the identity phi4(2x)=0.0625*(x*phi3(x)+0.5)^2+0.125*(phi3(x)+phi4(x));
// where phi3(x)=x*phi4(x)+1/6
real phi4(real x)
{
if(fabs(x) > 1.6) return (exp(x)-Coeff[2]*x^3-0.5*x^2-x-1)/x^4;
x *= 0.125;
real x2=x*x;
real x3=x2*x;
real x4=x2*x2;
real x5=x2*x3;
real y=Coeff[3]+x*Coeff[4]+x2*Coeff[5]+x3*Coeff[6]+
x4*Coeff[7]+x5*Coeff[8]+x3*x3*Coeff[9]+x5*x2*Coeff[10]+
x4*x4*Coeff[11];
real y3=x*y+Coeff[2];
y=0.0625*(x*y3+0.5)^2+0.125*(y3+y);
y3=2*x*y+Coeff[2];
y=(0.5*x*y3+0.125)^2+0.125*(y3+y);
y3=4*x*y+Coeff[2];
return (x*y3+0.125)^2+0.125*(y3+y);
}
void expfactors(real x, coefficients a)
{
for(int i=0; i < a.steps.length; ++i)
a.factors[i]=exp(x*a.steps[i]);
a.factors[a.steps.length]=exp(x);
}
// First-Order Euler
RKTableau Euler=RKTableau(1,new real[][], new real[] {1});
// First-Order Exponential Euler
RKTableau E_Euler=RKTableau(1,new real[][], new real[] {1},
new void(real h, real c, coefficients a) {
real x=-c*h;
expfactors(x,a);
a.highOrderWeights[0]=phi1(x);
});
// Second-Order Runge-Kutta
RKTableau RK2=RKTableau(2,new real[][] {{1/2}},
new real[] {0,1}, // 2nd order
new real[] {1,0}); // 1st order
// Second-Order Exponential Runge-Kutta
RKTableau E_RK2=RKTableau(2,new real[][] {{1/2}},
new real[] {0,1}, // 2nd order
new real[] {1,0}, // 1st order
new void(real h, real c, coefficients a) {
real x=-c*h;
expfactors(x,a);
a.weights[0][0]=1/2*phi1(x/2);
real w=phi1(x);
a.highOrderWeights[0]=0;
a.highOrderWeights[1]=w;
a.lowOrderWeights[0]=w;
});
// Second-Order Predictor-Corrector
RKTableau PC=RKTableau(2,new real[][] {{1}},
new real[] {1/2,1/2}, // 2nd order
new real[] {1,0}); // 1st order
// Second-Order Exponential Predictor-Corrector
RKTableau E_PC=RKTableau(2,new real[][] {{1}},
new real[] {1/2,1/2}, // 2nd order
new real[] {1,0}, // 1st order
new void(real h, real c, coefficients a) {
real x=-c*h;
expfactors(x,a);
real w=phi1(x);
a.weights[0][0]=w;
a.highOrderWeights[0]=w/2;
a.highOrderWeights[1]=w/2;
a.lowOrderWeights[0]=w;
});
// Third-Order Bogacki-Shampine Runge-Kutta
RKTableau RK3BS=RKTableau(3,new real[][] {{1/2},{0,3/4}},
new real[] {2/9,1/3,4/9}, // 3rd order
new real[] {7/24,1/4,1/3,1/8}); // 2nd order
// Third-Order Exponential Bogacki-Shampine Runge-Kutta
RKTableau E_RK3BS=RKTableau(3,new real[][] {{1/2},{0,3/4}},
new real[] {2/9,1/3,4/9}, // 3rd order
new real[] {7/24,1/4,1/3,1/8}, // 2nd order
new void(real h, real c, coefficients a) {
real x=-c*h;
expfactors(x,a);
real w=phi1(x);
real w2=phi2(x);
a.weights[0][0]=1/2*phi1(x/2);
real a11=9/8*phi2(3/4*x)+3/8*phi2(x/2);
a.weights[1][0]=3/4*phi1(3/4*x)-a11;
a.weights[1][1]=a11;
real a21=1/3*w;
real a22=4/3*w2-2/9*w;
a.highOrderWeights[0]=w-a21-a22;
a.highOrderWeights[1]=a21;
a.highOrderWeights[2]=a22;
a.lowOrderWeights[0]=w-17/12*w2;
a.lowOrderWeights[1]=w2/2;
a.lowOrderWeights[2]=2/3*w2;
a.lowOrderWeights[3]=w2/4;
});
// Fifth-Order Cash-Karp Runge-Kutta
RKTableau RK5=RKTableau(5,new real[][] {{1/5},
{3/40,9/40},
{3/10,-9/10,6/5},
{-11/54,5/2,-70/27,35/27},
{1631/55296,175/512,575/13824,
44275/110592,253/4096}},
new real[] {37/378,0,250/621,125/594,
0,512/1771}, // 5th order
new real[] {2825/27648,0,18575/48384,13525/55296,
277/14336,1/4}); // 4th order
// Fifth-Order Fehlberg Runge-Kutta
RKTableau RK5F=RKTableau(5,new real[][] {{1/4},
{3/32,9/32},
{1932/2197,-7200/2197,7296/2197},
{439/216,-8,3680/513,-845/4104},
{-8/27,2,-3544/2565,1859/4104,
-11/40}},
new real[] {16/135,0,6656/12825,28561/56430,-9/50,2/55}, // 5th order
new real[] {25/216,0,1408/2565,2197/4104,-1/5,0}); // 4th order
// Fifth-Order Dormand-Prince Runge-Kutta
RKTableau RK5DP=RKTableau(5,new real[][] {{1/5},
{3/40,9/40},
{44/45,-56/15,32/9},
{19372/6561,-25360/2187,64448/6561,
-212/729},
{9017/3168,-355/33,46732/5247,49/176,
-5103/18656}},
new real[] {35/384,0,500/1113,125/192,-2187/6784,
11/84}, // 5th order
new real[] {5179/57600,0,7571/16695,393/640,
-92097/339200,187/2100,1/40}); // 4th order
real error(real error, real initial, real lowOrder, real norm, real diff)
{
if(initial != 0 && lowOrder != initial) {
static real epsilon=realMin/realEpsilon;
real denom=max(abs(norm),abs(initial))+epsilon;
return max(error,max(abs(diff)/denom));
}
return error;
}
void report(real old, real h, real t)
{
write("Time step changed from "+(string) old+" to "+(string) h+" at t="+
(string) t+".");
}
real adjust(real h, real error, real tolmin, real tolmax, RKTableau tableau)
{
if(error > tolmax)
h *= max((tolmin/error)^tableau.pshrink,1/stepfactor);
else if(error > 0 && error < tolmin)
h *= min((tolmin/error)^tableau.pgrow,stepfactor);
return h;
}
// Integrate dy/dt+cy=f(t,y) from a to b using initial conditions y,
// specifying either the step size h or the number of steps n.
solution integrate(real y, real c=0, real f(real t, real y), real a, real b=a,
real h=0, int n=0, bool dynamic=false, real tolmin=0,
real tolmax=0, real dtmin=0, real dtmax=realMax,
RKTableau tableau, bool verbose=false)
{
solution S;
S.t=new real[] {a};
S.y=new real[] {y};
if(h == 0) {
if(b == a) return S;
if(n == 0) abort("Either n or h must be specified");
else h=(b-a)/n;
}
real F(real t, real y)=(c == 0 || tableau.exponential) ? f :
new real(real t, real y) {return f(t,y)-c*y;};
tableau.stepDependence(h,c,tableau.a);
real t=a;
real f0;
if(tableau.a.lowOrderWeights.length == 0) dynamic=false;
bool fsal=dynamic &&
(tableau.a.lowOrderWeights.length > tableau.a.highOrderWeights.length);
if(fsal) f0=F(t,y);
real dt=h;
while(t < b) {
h=min(h,b-t);
if(t+h == t) break;
if(h != dt) {
if(verbose) report(dt,h,t);
tableau.stepDependence(h,c,tableau.a);
dt=h;
}
real highOrder=h*dot(tableau.a.highOrderWeights,predictions);
real y0=tableau.a.factors[tableau.a.steps.length]*y;
if(dynamic) {
real f1;
if(fsal) {
f1=F(t+h,y0+highOrder);
predictions.push(f1);
}
real lowOrder=h*dot(tableau.a.lowOrderWeights,predictions);
real error;
error=error(error,y,y0+lowOrder,y0+highOrder,highOrder-lowOrder);
h=adjust(h,error,tolmin,tolmax,tableau);
if(h >= dt) {
t += dt;
y=y0+highOrder;
S.t.push(t);
S.y.push(y);
f0=f1;
}
h=min(max(h,dtmin),dtmax);
} else {
t += h;
y=y0+highOrder;
S.t.push(t);
S.y.push(y);
}
}
return S;
}
struct Solution
{
real[] t;
real[][] y;
}
void write(Solution S)
{
for(int i=0; i < S.t.length; ++i) {
write(S.t[i],tab);
for(real y : S.y[i])
write(y,tab);
write();
}
}
// Integrate a set of equations, dy/dt=f(t,y), from a to b using initial
// conditions y, specifying either the step size h or the number of steps n.
Solution integrate(real[] y, real[] f(real t, real[] y), real a, real b=a,
real h=0, int n=0, bool dynamic=false,
real tolmin=0, real tolmax=0, real dtmin=0,
real dtmax=realMax, RKTableau tableau, bool verbose=false)
{
Solution S;
S.t=new real[] {a};
S.y=new real[][] {copy(y)};
if(h == 0) {
if(b == a) return S;
if(n == 0) abort("Either n or h must be specified");
else h=(b-a)/n;
}
real t=a;
real[] f0;
if(tableau.a.lowOrderWeights.length == 0) dynamic=false;
bool fsal=dynamic &&
(tableau.a.lowOrderWeights.length > tableau.a.highOrderWeights.length);
if(fsal) f0=f(t,y);
real dt=h;
while(t < b) {
h=min(h,b-t);
if(t+h == t) break;
if(h != dt) {
if(verbose) report(dt,h,t);
dt=h;
}