/// <summary> /// Solves the differential equation $y'=f(t,y)$ from $t=s_0$ to $t=s$ with initial value $y(s_0)=y_0$. Used by /// <see cref="SolveCashKarpAdaptive" /> and <see cref="SolveVernerAdaptive" />. /// </summary> public static Vector SolveAdaptive(Func<double, Vector, Vector> f, Vector y0, double s0, double s, double eps, double h0, double hmin, StepperAdaptive stepper, out double[] t) { List<double> tsteps = new List<double>(); Vector y = SolveAdaptive(f, y0, s0, s, eps, h0, hmin, stepper, tsteps); t = tsteps.ToArray(); return y; }
private static Vector SolveAdaptive(Func<double, Vector, Vector> f, Vector y0, double s0, double s, double eps, double h0, double hmin, StepperAdaptive stepper, List<double> tsteps) { if (f == null || y0 == null || stepper == null) { throw new ArgumentNullException(); } if (eps <= 0.0) { throw new ArgumentOutOfRangeException("Invalid target accuracy."); } if (hmin < 0.0 || h0 < hmin || h0 == 0.0) { throw new ArgumentOutOfRangeException("Invalid step size specification."); } // Use initial value to determine the dimension of the problem. int n = y0.Length; // Use this step size for the first step (unless determined too large). double h = h0; // Negative step size if integrating backwards through time. if (s < s0) { h = -h; hmin = -hmin; } double t = s0; Vector y = y0; while (t != s) { // Store intermediate steps if allocated. if (tsteps != null) { tsteps.Add(t); } // Try this step. And maintain a minimum step size. double u = t + h; double umin = t + hmin; // Don't allow the current step to overshoot. if (s > s0 && u > s || s < s0 && u < s) { u = s; } // May take a smaller step than the minimum step size at the very last step to avoid overshooting. if (s > s0 && umin > s || s < s0 && umin < s) { umin = s; } // Evaluate derivative at the initial point for this step. This may be reused even if guessed step size is too large. Vector dy = f(t, y); Vector z; while (true) { // Don't go below the minimum step size. if (s > s0 && u < umin || s < s0 && u > umin) { u = umin; h = hmin; } if (u == t) { throw new ArithmeticException("Adaptive step size underflow."); } // Take a step. Vector yerr; z = stepper(f, y, t, u, dy, out yerr); // Evaluate accuracy. And scale relative to required tolerance. double errmax = 0.0; for (int i = 0; i < n; i++) { // Scaling to monitor accuracy. This is a general-purpose choice. double yscal = Math.Abs(y[i]) + Math.Abs(dy[i] * h); // Need this to be non-zero. Otherwise the entry can't be that important. if (yscal != 0.0) { errmax = Math.Max(errmax, Math.Abs(yerr[i] / yscal)); } } errmax /= eps; if (errmax <= 1.0) { // Step succeeded. Increase the step size for the next step, but no more than a factor of 5. h *= Math.Min(5.0, 0.9 * Math.Pow(errmax, -0.2)); break; } if (u == umin) { // Already at minimum step size. Not allowed to improve the step size below this. break; } // Not succeeded: Truncation error too large, reduce stepsize, but no more than a factor of 10. h *= Math.Max(0.1, 0.9 * Math.Pow(errmax, -0.25)); u = t + h; // Restart loop with the reduced step size. } // Finish the step. t = u; y = z; } return y; }
/// <summary> /// Solves the differential equation $y'=f(t,y)$ from $t=s_0$ to $t=s$ with initial value $y(s_0)=y_0$. Used by /// <see cref="SolveCashKarpAdaptive" /> and <see cref="SolveVernerAdaptive" />. /// </summary> public static Vector SolveAdaptive(Func<double, Vector, Vector> f, Vector y0, double s0, double s, double eps, double h0, double hmin, StepperAdaptive stepper) { return SolveAdaptive(f, y0, s0, s, eps, h0, hmin, stepper, null); }