ContinuousOutputModel.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.math4.legacy.ode;

  18. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.List;

  21. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  22. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  23. import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
  24. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  25. import org.apache.commons.math4.legacy.ode.sampling.StepHandler;
  26. import org.apache.commons.math4.legacy.ode.sampling.StepInterpolator;
  27. import org.apache.commons.math4.core.jdkmath.JdkMath;

  28. /**
  29.  * This class stores all information provided by an ODE integrator
  30.  * during the integration process and build a continuous model of the
  31.  * solution from this.
  32.  *
  33.  * <p>This class act as a step handler from the integrator point of
  34.  * view. It is called iteratively during the integration process and
  35.  * stores a copy of all steps information in a sorted collection for
  36.  * later use. Once the integration process is over, the user can use
  37.  * the {@link #setInterpolatedTime setInterpolatedTime} and {@link
  38.  * #getInterpolatedState getInterpolatedState} to retrieve this
  39.  * information at any time. It is important to wait for the
  40.  * integration to be over before attempting to call {@link
  41.  * #setInterpolatedTime setInterpolatedTime} because some internal
  42.  * variables are set only once the last step has been handled.</p>
  43.  *
  44.  * <p>This is useful for example if the main loop of the user
  45.  * application should remain independent from the integration process
  46.  * or if one needs to mimic the behaviour of an analytical model
  47.  * despite a numerical model is used (i.e. one needs the ability to
  48.  * get the model value at any time or to navigate through the
  49.  * data).</p>
  50.  *
  51.  * <p>If problem modeling is done with several separate
  52.  * integration phases for contiguous intervals, the same
  53.  * ContinuousOutputModel can be used as step handler for all
  54.  * integration phases as long as they are performed in order and in
  55.  * the same direction. As an example, one can extrapolate the
  56.  * trajectory of a satellite with one model (i.e. one set of
  57.  * differential equations) up to the beginning of a maneuver, use
  58.  * another more complex model including thrusters modeling and
  59.  * accurate attitude control during the maneuver, and revert to the
  60.  * first model after the end of the maneuver. If the same continuous
  61.  * output model handles the steps of all integration phases, the user
  62.  * do not need to bother when the maneuver begins or ends, he has all
  63.  * the data available in a transparent manner.</p>
  64.  *
  65.  * <p>An important feature of this class is that it implements the
  66.  * <code>Serializable</code> interface. This means that the result of
  67.  * an integration can be serialized and reused later (if stored into a
  68.  * persistent medium like a file system or a database) or elsewhere (if
  69.  * sent to another application). Only the result of the integration is
  70.  * stored, there is no reference to the integrated problem by
  71.  * itself.</p>
  72.  *
  73.  * <p>One should be aware that the amount of data stored in a
  74.  * ContinuousOutputModel instance can be important if the state vector
  75.  * is large, if the integration interval is long or if the steps are
  76.  * small (which can result from small tolerance settings in {@link
  77.  * org.apache.commons.math4.legacy.ode.nonstiff.AdaptiveStepsizeIntegrator adaptive
  78.  * step size integrators}).</p>
  79.  *
  80.  * @see StepHandler
  81.  * @see StepInterpolator
  82.  * @since 1.2
  83.  */

  84. public class ContinuousOutputModel
  85.   implements StepHandler, Serializable {

  86.     /** Serializable version identifier. */
  87.     private static final long serialVersionUID = -1417964919405031606L;

  88.     /** Initial integration time. */
  89.     private double initialTime;

  90.     /** Final integration time. */
  91.     private double finalTime;

  92.     /** Integration direction indicator. */
  93.     private boolean forward;

  94.     /** Current interpolator index. */
  95.     private int index;

  96.     /** Steps table. */
  97.     private List<StepInterpolator> steps;

  98.   /** Simple constructor.
  99.    * Build an empty continuous output model.
  100.    */
  101.   public ContinuousOutputModel() {
  102.     steps = new ArrayList<>();
  103.     initialTime = Double.NaN;
  104.     finalTime   = Double.NaN;
  105.     forward     = true;
  106.     index       = 0;
  107.   }

  108.   /** Append another model at the end of the instance.
  109.    * @param model model to add at the end of the instance
  110.    * @exception MathIllegalArgumentException if the model to append is not
  111.    * compatible with the instance (dimension of the state vector,
  112.    * propagation direction, hole between the dates)
  113.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  114.    * during step finalization
  115.    */
  116.   public void append(final ContinuousOutputModel model)
  117.     throws MathIllegalArgumentException, MaxCountExceededException {

  118.     if (model.steps.isEmpty()) {
  119.       return;
  120.     }

  121.     if (steps.isEmpty()) {
  122.       initialTime = model.initialTime;
  123.       forward     = model.forward;
  124.     } else {

  125.       if (getInterpolatedState().length != model.getInterpolatedState().length) {
  126.           throw new DimensionMismatchException(model.getInterpolatedState().length,
  127.                                                getInterpolatedState().length);
  128.       }

  129.       if (forward ^ model.forward) {
  130.           throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
  131.       }

  132.       final StepInterpolator lastInterpolator = steps.get(index);
  133.       final double current  = lastInterpolator.getCurrentTime();
  134.       final double previous = lastInterpolator.getPreviousTime();
  135.       final double step = current - previous;
  136.       final double gap = model.getInitialTime() - current;
  137.       if (JdkMath.abs(gap) > 1.0e-3 * JdkMath.abs(step)) {
  138.         throw new MathIllegalArgumentException(LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
  139.                                                JdkMath.abs(gap));
  140.       }
  141.     }

  142.     for (StepInterpolator interpolator : model.steps) {
  143.       steps.add(interpolator.copy());
  144.     }

  145.     index = steps.size() - 1;
  146.     finalTime = (steps.get(index)).getCurrentTime();
  147.   }

  148.   /** {@inheritDoc} */
  149.   @Override
  150.   public void init(double t0, double[] y0, double t) {
  151.     initialTime = Double.NaN;
  152.     finalTime   = Double.NaN;
  153.     forward     = true;
  154.     index       = 0;
  155.     steps.clear();
  156.   }

  157.   /** Handle the last accepted step.
  158.    * A copy of the information provided by the last step is stored in
  159.    * the instance for later use.
  160.    * @param interpolator interpolator for the last accepted step.
  161.    * @param isLast true if the step is the last one
  162.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  163.    * during step finalization
  164.    */
  165.   @Override
  166. public void handleStep(final StepInterpolator interpolator, final boolean isLast)
  167.       throws MaxCountExceededException {

  168.     if (steps.isEmpty()) {
  169.       initialTime = interpolator.getPreviousTime();
  170.       forward     = interpolator.isForward();
  171.     }

  172.     steps.add(interpolator.copy());

  173.     if (isLast) {
  174.       finalTime = interpolator.getCurrentTime();
  175.       index     = steps.size() - 1;
  176.     }
  177.   }

  178.   /**
  179.    * Get the initial integration time.
  180.    * @return initial integration time
  181.    */
  182.   public double getInitialTime() {
  183.     return initialTime;
  184.   }

  185.   /**
  186.    * Get the final integration time.
  187.    * @return final integration time
  188.    */
  189.   public double getFinalTime() {
  190.     return finalTime;
  191.   }

  192.   /**
  193.    * Get the time of the interpolated point.
  194.    * If {@link #setInterpolatedTime} has not been called, it returns
  195.    * the final integration time.
  196.    * @return interpolation point time
  197.    */
  198.   public double getInterpolatedTime() {
  199.     return steps.get(index).getInterpolatedTime();
  200.   }

  201.   /** Set the time of the interpolated point.
  202.    * <p>This method should <strong>not</strong> be called before the
  203.    * integration is over because some internal variables are set only
  204.    * once the last step has been handled.</p>
  205.    * <p>Setting the time outside of the integration interval is now
  206.    * allowed, but should be used with care since the accuracy of the
  207.    * interpolator will probably be very poor far from this interval.
  208.    * This allowance has been added to simplify implementation of search
  209.    * algorithms near the interval endpoints.</p>
  210.    * <p>Note that each time this method is called, the internal arrays
  211.    * returned in {@link #getInterpolatedState()}, {@link
  212.    * #getInterpolatedDerivatives()} and {@link #getInterpolatedSecondaryState(int)}
  213.    * <em>will</em> be overwritten. So if their content must be preserved
  214.    * across several calls, user must copy them.</p>
  215.    * @param time time of the interpolated point
  216.    * @see #getInterpolatedState()
  217.    * @see #getInterpolatedDerivatives()
  218.    * @see #getInterpolatedSecondaryState(int)
  219.    */
  220.   public void setInterpolatedTime(final double time) {

  221.       // initialize the search with the complete steps table
  222.       int iMin = 0;
  223.       final StepInterpolator sMin = steps.get(iMin);
  224.       double tMin = 0.5 * (sMin.getPreviousTime() + sMin.getCurrentTime());

  225.       int iMax = steps.size() - 1;
  226.       final StepInterpolator sMax = steps.get(iMax);
  227.       double tMax = 0.5 * (sMax.getPreviousTime() + sMax.getCurrentTime());

  228.       // handle points outside of the integration interval
  229.       // or in the first and last step
  230.       if (locatePoint(time, sMin) <= 0) {
  231.         index = iMin;
  232.         sMin.setInterpolatedTime(time);
  233.         return;
  234.       }
  235.       if (locatePoint(time, sMax) >= 0) {
  236.         index = iMax;
  237.         sMax.setInterpolatedTime(time);
  238.         return;
  239.       }

  240.       // reduction of the table slice size
  241.       while (iMax - iMin > 5) {

  242.         // use the last estimated index as the splitting index
  243.         final StepInterpolator si = steps.get(index);
  244.         final int location = locatePoint(time, si);
  245.         if (location < 0) {
  246.           iMax = index;
  247.           tMax = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
  248.         } else if (location > 0) {
  249.           iMin = index;
  250.           tMin = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
  251.         } else {
  252.           // we have found the target step, no need to continue searching
  253.           si.setInterpolatedTime(time);
  254.           return;
  255.         }

  256.         // compute a new estimate of the index in the reduced table slice
  257.         final int iMed = (iMin + iMax) / 2;
  258.         final StepInterpolator sMed = steps.get(iMed);
  259.         final double tMed = 0.5 * (sMed.getPreviousTime() + sMed.getCurrentTime());

  260.         if (JdkMath.abs(tMed - tMin) < 1e-6 || JdkMath.abs(tMax - tMed) < 1e-6) {
  261.           // too close to the bounds, we estimate using a simple dichotomy
  262.           index = iMed;
  263.         } else {
  264.           // estimate the index using a reverse quadratic polynom
  265.           // (reverse means we have i = P(t), thus allowing to simply
  266.           // compute index = P(time) rather than solving a quadratic equation)
  267.           final double d12 = tMax - tMed;
  268.           final double d23 = tMed - tMin;
  269.           final double d13 = tMax - tMin;
  270.           final double dt1 = time - tMax;
  271.           final double dt2 = time - tMed;
  272.           final double dt3 = time - tMin;
  273.           final double iLagrange = ((dt2 * dt3 * d23) * iMax -
  274.                                     (dt1 * dt3 * d13) * iMed +
  275.                                     (dt1 * dt2 * d12) * iMin) /
  276.                                    (d12 * d23 * d13);
  277.           index = (int) JdkMath.rint(iLagrange);
  278.         }

  279.         // force the next size reduction to be at least one tenth
  280.         final int low  = JdkMath.max(iMin + 1, (9 * iMin + iMax) / 10);
  281.         final int high = JdkMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
  282.         if (index < low) {
  283.           index = low;
  284.         } else if (index > high) {
  285.           index = high;
  286.         }
  287.       }

  288.       // now the table slice is very small, we perform an iterative search
  289.       index = iMin;
  290.       while (index <= iMax && locatePoint(time, steps.get(index)) > 0) {
  291.         ++index;
  292.       }

  293.       steps.get(index).setInterpolatedTime(time);
  294.   }

  295.   /**
  296.    * Get the state vector of the interpolated point.
  297.    * <p>The returned vector is a reference to a reused array, so
  298.    * it should not be modified and it should be copied if it needs
  299.    * to be preserved across several calls to the associated
  300.    * {@link #setInterpolatedTime(double)} method.</p>
  301.    * @return state vector at time {@link #getInterpolatedTime}
  302.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  303.    * @see #setInterpolatedTime(double)
  304.    * @see #getInterpolatedDerivatives()
  305.    * @see #getInterpolatedSecondaryState(int)
  306.    * @see #getInterpolatedSecondaryDerivatives(int)
  307.    */
  308.   public double[] getInterpolatedState() throws MaxCountExceededException {
  309.     return steps.get(index).getInterpolatedState();
  310.   }

  311.   /**
  312.    * Get the derivatives of the state vector of the interpolated point.
  313.    * <p>The returned vector is a reference to a reused array, so
  314.    * it should not be modified and it should be copied if it needs
  315.    * to be preserved across several calls to the associated
  316.    * {@link #setInterpolatedTime(double)} method.</p>
  317.    * @return derivatives of the state vector at time {@link #getInterpolatedTime}
  318.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  319.    * @see #setInterpolatedTime(double)
  320.    * @see #getInterpolatedState()
  321.    * @see #getInterpolatedSecondaryState(int)
  322.    * @see #getInterpolatedSecondaryDerivatives(int)
  323.    * @since 3.4
  324.    */
  325.   public double[] getInterpolatedDerivatives() throws MaxCountExceededException {
  326.     return steps.get(index).getInterpolatedDerivatives();
  327.   }

  328.   /** Get the interpolated secondary state corresponding to the secondary equations.
  329.    * <p>The returned vector is a reference to a reused array, so
  330.    * it should not be modified and it should be copied if it needs
  331.    * to be preserved across several calls to the associated
  332.    * {@link #setInterpolatedTime(double)} method.</p>
  333.    * @param secondaryStateIndex index of the secondary set, as returned by {@link
  334.    * org.apache.commons.math4.legacy.ode.ExpandableStatefulODE#addSecondaryEquations(
  335.    * org.apache.commons.math4.legacy.ode.SecondaryEquations)
  336.    * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
  337.    * @return interpolated secondary state at the current interpolation date
  338.    * @see #setInterpolatedTime(double)
  339.    * @see #getInterpolatedState()
  340.    * @see #getInterpolatedDerivatives()
  341.    * @see #getInterpolatedSecondaryDerivatives(int)
  342.    * @since 3.2
  343.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  344.    */
  345.   public double[] getInterpolatedSecondaryState(final int secondaryStateIndex)
  346.     throws MaxCountExceededException {
  347.     return steps.get(index).getInterpolatedSecondaryState(secondaryStateIndex);
  348.   }

  349.   /** Get the interpolated secondary derivatives corresponding to the secondary equations.
  350.    * <p>The returned vector is a reference to a reused array, so
  351.    * it should not be modified and it should be copied if it needs
  352.    * to be preserved across several calls to the associated
  353.    * {@link #setInterpolatedTime(double)} method.</p>
  354.    * @param secondaryStateIndex index of the secondary set, as returned by {@link
  355.    * org.apache.commons.math4.legacy.ode.ExpandableStatefulODE#addSecondaryEquations(
  356.    * org.apache.commons.math4.legacy.ode.SecondaryEquations)
  357.    * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
  358.    * @return interpolated secondary derivatives at the current interpolation date
  359.    * @see #setInterpolatedTime(double)
  360.    * @see #getInterpolatedState()
  361.    * @see #getInterpolatedDerivatives()
  362.    * @see #getInterpolatedSecondaryState(int)
  363.    * @since 3.4
  364.    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  365.    */
  366.   public double[] getInterpolatedSecondaryDerivatives(final int secondaryStateIndex)
  367.     throws MaxCountExceededException {
  368.     return steps.get(index).getInterpolatedSecondaryDerivatives(secondaryStateIndex);
  369.   }

  370.   /** Compare a step interval and a double.
  371.    * @param time point to locate
  372.    * @param interval step interval
  373.    * @return -1 if the double is before the interval, 0 if it is in
  374.    * the interval, and +1 if it is after the interval, according to
  375.    * the interval direction
  376.    */
  377.   private int locatePoint(final double time, final StepInterpolator interval) {
  378.     if (forward) {
  379.       if (time < interval.getPreviousTime()) {
  380.         return -1;
  381.       } else if (time > interval.getCurrentTime()) {
  382.         return +1;
  383.       } else {
  384.         return 0;
  385.       }
  386.     }
  387.     if (time > interval.getPreviousTime()) {
  388.       return -1;
  389.     } else if (time < interval.getCurrentTime()) {
  390.       return +1;
  391.     } else {
  392.       return 0;
  393.     }
  394.   }
  395. }