//
  // rkckStep takes a Cash-Karp Runge-Kutta step
  // to estimate the error and the substance levels
  // after the attempted step.
  //
  private void rkckStep(double h) {
    double t = timer.getTime();

    // step 1
    for (int i = 0; i < numberOfSubstances; i++) {
      //
      // get the substance values before this step
      //
      y1[i] = s[i].getValue();
      //
      // k1 = h * f(t, y);
      //
      k1[i] = h * s[i].getRate();
    }

    // step 2
    //
    // k2 = h * f (t + a2 * h, y + b21 * k1)
    //
    timer.setTime(t + a2 * h);
    for (int i = 0; i < numberOfSubstances; i++) {

      s[i].setValue(y1[i] + k1[i] * b21);
      k2[i] = h * s[i].getRate();
    }

    // step 3
    //
    // k3 = h * f (t + a3 * h, y + b31 * k1 + b32 * k2)
    //
    timer.setTime(t + a3 * h);
    for (int i = 0; i < numberOfSubstances; i++) {
      s[i].setValue(y1[i] + b31 * k1[i] + b32 * k2[i]);
      k3[i] = h * s[i].getRate();
    }

    // step 4
    //
    // k4 = h * f (t + a4 * h, y + b41 * k1 + b42 * k2 + b43 * k3)
    //
    timer.setTime(t + a4 * h);
    for (int i = 0; i < numberOfSubstances; i++) {

      s[i].setValue(y1[i] + b41 * k1[i] + b42 * k2[i] + b43 * k3[i]);
      k4[i] = h * s[i].getRate();
    }

    // step 5
    // k5 = h * f (t + a5 * h, y + b51 * k1 + b52 * k2 + b53 * k3 + b54 *
    // k4)
    //
    timer.setTime(t + a5 * h);
    for (int i = 0; i < numberOfSubstances; i++) {
      s[i].setValue(y1[i] + b51 * k1[i] + b52 * k2[i] + b53 * k3[i] + b54 * k4[i]);
      k5[i] = h * s[i].getRate();
    }

    // step 6
    // k6 = h * f (t + a6 * h, y + b61 * k1 + b62 * k2 + b63 * k3 + b64 * k4
    // + k65 * k5)
    //
    timer.setTime(t + a5 * h);
    for (int i = 0; i < numberOfSubstances; i++) {
      s[i].setValue(y1[i] + b61 * k1[i] + b62 * k2[i] + b63 * k3[i] + b64 * k4[i] + b65 * k5[i]);
      k6[i] = h * s[i].getRate();
    }

    for (int i = 0; i < numberOfSubstances; i++) {
      y2[i] = y1[i] + c1 * k1[i] + c3 * k3[i] + c4 * k4[i] + c6 * k6[i]; // fifth
      // order
      // estimate

      //
      // error between 4th and 5th
      //
      // note that the errors are scaled with respect to estimated new
      // values
      // if the new values are smaller than 1e-8, scale to 1e-8. This
      // number is
      // used to avoid scaling with respect to a number that is too small.
      //
      yerr[i] =
          Math.abs(
              (dc1 * k1[i] + dc3 * k3[i] + dc4 * k4[i] + dc5 * k5[i] + dc6 * k6[i])
                  / Math.max(Math.abs(y2[i]), 1e-8));
    }
  }