public void update() {
    double t = 0.0;
    double errRatio;
    if (step > samplingStep) step = samplingStep;
    int nSteps = 0;
    for (; ; ) {
      if ((samplingStep - t < step) && (samplingStep - t > 0.0)) step = samplingStep - t;

      rkckStep(step);
      errRatio = Numerics.max(yerr) / tolerance;

      //
      // Update the substance levels and go to the next step if
      // 1. The target tolerance is reached.
      // 2. The maximum # of iteractions is exceeded.
      // 3. The minimum time step is reached.
      //
      if (errRatio <= 1.0) {
        t += step;
        updateSystem(step);
        //
        // grow the step a little bit. but not more than a factor of
        // five
        //
        step *= Math.min(SAFETY * Math.pow(errRatio, PGROW), 5.0);
      } else if (nSteps > maxIterations) {
        System.out.println(" WARNING: # of iterations exceeds " + maxIterations);
        t += step;
        updateSystem(step);
      } else if (step < minStep) {
        System.out.println(" WARNING: Minimum integrateion step size (" + minStep + ") reached");
        t += step;
        updateSystem(step);
      } else {
        // step size too large.
        // Need to reset the values of all the substances
        //
        for (int i = 0; i < numberOfSubstances; i++) s[i].setValue(y1[i]);
        //
        // reset the Timer back to where it started.
        //
        timer.step(0 - step);

        //
        // shrink the step size
        // and recompute. But shrink by a factor smaller than 10.
        //
        step *= Math.max(SAFETY * Math.pow(errRatio, PSHRINK), 0.1);
      }
      if (t >= samplingStep) break;
    }

    //
    // Store the data if the sampling step is reached.
    //
    timer.storeTimePoint();
    for (int i = 0; i < numberOfSubstances; i++) {
      s[i].storeValue();
      s[i].storeRate();
    }
  }
  //
  // 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));
    }
  }