package org.spaceroots.mantissa.ode;

/**
 * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary
 * Differential Equations.

 * <p>This integrator is an embedded Runge-Kutta-Fehlberg integrator
 * of order 8(5,3) used in local extrapolation mode (i.e. the solution
 * is computed using the high order formula) with stepsize control
 * (and automatic step initialization) and continuous output. This
 * method uses 12 functions evaluations per step for integration and 4
 * evaluations for interpolation. However, since the first
 * interpolation evaluation is the same as the first integration
 * evaluation of the next step, we have included it in the integrator
 * rather than in the interpolator and specified the method was an
 * <i>fsal</i>. Hence, despite we have 13 stages here, the cost is
 * really 12 evaluations per step even if no interpolation is done,
 * and the overcost of interpolation is only 3 evaluations.</p>

 * <p>This method is based on an 8(6) method by Dormand and Prince
 * (i.e. order 8 for the integration and order 6 for error estimation)
 * modified by Hairer and Wanner to use a 5th order error estimator
 * with 3rd order correction. This modification was introduced because
 * the original method failed in some cases (wrong steps can be
 * accepted when step size is too large, for example in the
 * Brusselator problem) and also had <i>severe difficulties when
 * applied to problems with discontinuities</i>. This modification is
 * explained in the second edition of the first volume (Nonstiff
 * Problems) of the reference book by Hairer, Norsett and Wanner:
 * <i>Solving Ordinary Differential Equations</i> (Springer-Verlag,
 * ISBN 3-540-56670-8).</p>

 * @version $Id: DormandPrince853Integrator.java 1709 2006-12-03 21:16:50Z luc $
 * @author L. Maisonobe

 */

public class DormandPrince853Integrator
  extends RungeKuttaFehlbergIntegrator {

  private static final String methodName = "Dormand-Prince 8 (5, 3)";

  private static final double sqrt6 = Math.sqrt(6.0);

  private static final double[] c = {
    (12.0 - 2.0 * sqrt6) / 135.0, (6.0 - sqrt6) / 45.0, (6.0 - sqrt6) / 30.0,
    (6.0 + sqrt6) / 30.0, 1.0/3.0, 1.0/4.0, 4.0/13.0, 127.0/195.0, 3.0/5.0,
    6.0/7.0, 1.0, 1.0
  };

  private static final double[][] a = {

    // k2
    {(12.0 - 2.0 * sqrt6) / 135.0},

    // k3
    {(6.0 - sqrt6) / 180.0, (6.0 - sqrt6) / 60.0},

    // k4
    {(6.0 - sqrt6) / 120.0, 0.0, (6.0 - sqrt6) / 40.0},

    // k5
    {(462.0 + 107.0 * sqrt6) / 3000.0, 0.0,
     (-402.0 - 197.0 * sqrt6) / 1000.0, (168.0 + 73.0 * sqrt6) / 375.0},

    // k6
    {1.0 / 27.0, 0.0, 0.0, (16.0 + sqrt6) / 108.0, (16.0 - sqrt6) / 108.0},

    // k7
    {19.0 / 512.0, 0.0, 0.0, (118.0 + 23.0 * sqrt6) / 1024.0,
     (118.0 - 23.0 * sqrt6) / 1024.0, -9.0 / 512.0},

    // k8
    {13772.0 / 371293.0, 0.0, 0.0, (51544.0 + 4784.0 * sqrt6) / 371293.0,
     (51544.0 - 4784.0 * sqrt6) / 371293.0, -5688.0 / 371293.0, 3072.0 / 371293.0},

    // k9
    {58656157643.0 / 93983540625.0, 0.0, 0.0,
     (-1324889724104.0 - 318801444819.0 * sqrt6) / 626556937500.0,
     (-1324889724104.0 + 318801444819.0 * sqrt6) / 626556937500.0,
     96044563816.0 / 3480871875.0, 5682451879168.0 / 281950621875.0,
     -165125654.0 / 3796875.0},

    // k10
    {8909899.0 / 18653125.0, 0.0, 0.0,
     (-4521408.0 - 1137963.0 * sqrt6) / 2937500.0,
     (-4521408.0 + 1137963.0 * sqrt6) / 2937500.0,
     96663078.0 / 4553125.0, 2107245056.0 / 137915625.0,
     -4913652016.0 / 147609375.0, -78894270.0 / 3880452869.0},

    // k11
    {-20401265806.0 / 21769653311.0, 0.0, 0.0,
     (354216.0 + 94326.0 * sqrt6) / 112847.0,
     (354216.0 - 94326.0 * sqrt6) / 112847.0,
     -43306765128.0 / 5313852383.0, -20866708358144.0 / 1126708119789.0,
     14886003438020.0 / 654632330667.0, 35290686222309375.0 / 14152473387134411.0,
     -1477884375.0 / 485066827.0},

    // k12
    {39815761.0 / 17514443.0, 0.0, 0.0,
     (-3457480.0 - 960905.0 * sqrt6) / 551636.0,
     (-3457480.0 + 960905.0 * sqrt6) / 551636.0,
     -844554132.0 / 47026969.0, 8444996352.0 / 302158619.0,
     -2509602342.0 / 877790785.0, -28388795297996250.0 / 3199510091356783.0,
     226716250.0 / 18341897.0, 1371316744.0 / 2131383595.0},

    // k13 should be for interpolation only, but since it is the same
    // stage as the first evaluation of the next step, we perform it
    // here at no cost by specifying this is an fsal method
    {104257.0/1920240.0, 0.0, 0.0, 0.0, 0.0, 3399327.0/763840.0,
     66578432.0/35198415.0, -1674902723.0/288716400.0,
     54980371265625.0/176692375811392.0, -734375.0/4826304.0,
     171414593.0/851261400.0, 137909.0/3084480.0}

  };

  private static final double[] b = {
      104257.0/1920240.0,
      0.0,
      0.0,
      0.0,
      0.0,
      3399327.0/763840.0,
      66578432.0/35198415.0,
      -1674902723.0/288716400.0,
      54980371265625.0/176692375811392.0,
      -734375.0/4826304.0,
      171414593.0/851261400.0,
      137909.0/3084480.0,
      0.0
  };

  private static final double e1_01 =         116092271.0 / 8848465920.0;
  private static final double e1_06 =          -1871647.0 / 1527680.0;
  private static final double e1_07 =         -69799717.0 / 140793660.0;
  private static final double e1_08 =     1230164450203.0 / 739113984000.0;
  private static final double e1_09 = -1980813971228885.0 / 5654156025964544.0;
  private static final double e1_10 =         464500805.0 / 1389975552.0;
  private static final double e1_11 =     1606764981773.0 / 19613062656000.0;
  private static final double e1_12 =           -137909.0 / 6168960.0;

  private static final double e2_01 =           -364463.0 / 1920240.0;
  private static final double e2_06 =           3399327.0 / 763840.0;
  private static final double e2_07 =          66578432.0 / 35198415.0;
  private static final double e2_08 =       -1674902723.0 / 288716400.0;
  private static final double e2_09 =   -74684743568175.0 / 176692375811392.0;
  private static final double e2_10 =           -734375.0 / 4826304.0;
  private static final double e2_11 =         171414593.0 / 851261400.0;
  private static final double e2_12 =             69869.0 / 3084480.0;

  /** Simple constructor.
   * Build an eighth order Dormand-Prince integrator with the given step bounds
   * @param minStep minimal step (must be positive even for backward
   * integration), the last step can be smaller than this
   * @param maxStep maximal step (must be positive even for backward
   * integration)
   * @param scalAbsoluteTolerance allowed absolute error
   * @param scalRelativeTolerance allowed relative error
   */
  public DormandPrince853Integrator(double minStep, double maxStep,
                                    double scalAbsoluteTolerance,
                                    double scalRelativeTolerance) {
    super(true, c, a, b,
          new DormandPrince853StepInterpolator(),
          minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
  }

  /** Simple constructor.
   * Build an eighth order Dormand-Prince integrator with the given step bounds
   * @param minStep minimal step (must be positive even for backward
   * integration), the last step can be smaller than this
   * @param maxStep maximal step (must be positive even for backward
   * integration)
   * @param vecAbsoluteTolerance allowed absolute error
   * @param vecRelativeTolerance allowed relative error
   */
  public DormandPrince853Integrator(double minStep, double maxStep,
                                    double[] vecAbsoluteTolerance,
                                    double[] vecRelativeTolerance) {
    super(true, c, a, b,
          new DormandPrince853StepInterpolator(),
          minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
  }

  /** Get the name of the method.
   * @return name of the method
   */
  public String getName() {
    return methodName;
  }

  /** Get the order of the method.
   * @return order of the method
   */
  public int getOrder() {
    return 8;
  }

  /** Compute the error ratio.
   * @param yDotK derivatives computed during the first stages
   * @param y0 estimate of the step at the start of the step
   * @param y1 estimate of the step at the end of the step
   * @param h  current step
   * @return error ratio, greater than 1 if step should be rejected
   */
  protected double estimateError(double[][] yDotK,
                                 double[] y0, double[] y1,
                                 double h) {
    double error1 = 0;
    double error2 = 0;

    for (int j = 0; j < y0.length; ++j) {
      double errSum1 = e1_01 * yDotK[0][j]  + e1_06 * yDotK[5][j]
                     + e1_07 * yDotK[6][j]  + e1_08 * yDotK[7][j]
                     + e1_09 * yDotK[8][j]  + e1_10 * yDotK[9][j]
                     + e1_11 * yDotK[10][j] + e1_12 * yDotK[11][j];
      double errSum2 = e2_01 * yDotK[0][j]  + e2_06 * yDotK[5][j]
                     + e2_07 * yDotK[6][j]  + e2_08 * yDotK[7][j]
                     + e2_09 * yDotK[8][j]  + e2_10 * yDotK[9][j]
                     + e2_11 * yDotK[10][j] + e2_12 * yDotK[11][j];

      double yScale = Math.max(Math.abs(y0[j]), Math.abs(y1[j]));
      double tol = (vecAbsoluteTolerance == null)
        ? (scalAbsoluteTolerance + scalRelativeTolerance * yScale)
        : (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale);
      double ratio1  = errSum1 / tol;
      error1        += ratio1 * ratio1;
      double ratio2  = errSum2 / tol;
      error2        += ratio2 * ratio2;
    }

    double den = error1 + 0.01 * error2;
    if (den <= 0.0) {
      den = 1.0;
    }

    return Math.abs(h) * error1 / Math.sqrt(y0.length * den);

  }

}
