DenseOutputModel.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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.ode;

  22. import java.io.Serializable;
  23. import java.util.ArrayList;
  24. import java.util.List;

  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.exception.MathIllegalArgumentException;
  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.ode.sampling.ODEStateInterpolator;
  29. import org.hipparchus.ode.sampling.ODEStepHandler;
  30. import org.hipparchus.util.FastMath;

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

  86. public class DenseOutputModel implements ODEStepHandler, Serializable {

  87.     /** Serializable version identifier */
  88.     private static final long serialVersionUID = 20160328L;

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

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

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

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

  97.     /** Steps table. */
  98.     private List<ODEStateInterpolator> steps;

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

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

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

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

  126.             final ODEStateAndDerivative s1 = steps.get(0).getPreviousState();
  127.             final ODEStateAndDerivative s2 = model.steps.get(0).getPreviousState();
  128.             checkDimensionsEquality(s1.getPrimaryStateDimension(), s2.getPrimaryStateDimension());
  129.             checkDimensionsEquality(s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates());
  130.             for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) {
  131.                 checkDimensionsEquality(s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i));
  132.             }

  133.             if (forward ^ model.forward) {
  134.                 throw new MathIllegalArgumentException(LocalizedODEFormats.PROPAGATION_DIRECTION_MISMATCH);
  135.             }

  136.             final ODEStateInterpolator lastInterpolator = steps.get(index);
  137.             final double current  = lastInterpolator.getCurrentState().getTime();
  138.             final double previous = lastInterpolator.getPreviousState().getTime();
  139.             final double step = current - previous;
  140.             final double gap = model.getInitialTime() - current;
  141.             if (FastMath.abs(gap) > 1.0e-3 * FastMath.abs(step)) {
  142.                 throw new MathIllegalArgumentException(LocalizedODEFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
  143.                                                        FastMath.abs(gap));
  144.             }

  145.         }

  146.         for (ODEStateInterpolator interpolator : model.steps) {
  147.             steps.add(interpolator);
  148.         }

  149.         index = steps.size() - 1;
  150.         finalTime = (steps.get(index)).getCurrentState().getTime();

  151.     }

  152.     /** Check dimensions equality.
  153.      * @param d1 first dimension
  154.      * @param d2 second dimansion
  155.      * @exception MathIllegalArgumentException if dimensions do not match
  156.      */
  157.     private void checkDimensionsEquality(final int d1, final int d2)
  158.         throws MathIllegalArgumentException {
  159.         if (d1 != d2) {
  160.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  161.                                                    d2, d1);
  162.         }
  163.     }

  164.     /** {@inheritDoc} */
  165.     @Override
  166.     public void init(final ODEStateAndDerivative initialState, final double targetTime) {
  167.         initialTime    = initialState.getTime();
  168.         this.finalTime = targetTime;
  169.         forward        = true;
  170.         index          = 0;
  171.         steps.clear();
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public void handleStep(final ODEStateInterpolator interpolator) {
  176.         if (steps.isEmpty()) {
  177.             initialTime = interpolator.getPreviousState().getTime();
  178.             forward     = interpolator.isForward();
  179.         }
  180.         steps.add(interpolator);
  181.     }

  182.     /** {@inheritDoc} */
  183.     @Override
  184.     public void finish(final ODEStateAndDerivative finalState) {
  185.         finalTime = finalState.getTime();
  186.         index     = steps.size() - 1;
  187.     }

  188.     /**
  189.      * Get the initial integration time.
  190.      * @return initial integration time
  191.      */
  192.     public double getInitialTime() {
  193.         return initialTime;
  194.     }

  195.     /**
  196.      * Get the final integration time.
  197.      * @return final integration time
  198.      */
  199.     public double getFinalTime() {
  200.         return finalTime;
  201.     }

  202.     /**
  203.      * Get the state at interpolated time.
  204.      * @param time time of the interpolated point
  205.      * @return state at interpolated time
  206.      */
  207.     public ODEStateAndDerivative getInterpolatedState(final double time) {

  208.         // initialize the search with the complete steps table
  209.         int iMin = 0;
  210.         final ODEStateInterpolator sMin = steps.get(iMin);
  211.         double tMin = 0.5 * (sMin.getPreviousState().getTime() + sMin.getCurrentState().getTime());

  212.         int iMax = steps.size() - 1;
  213.         final ODEStateInterpolator sMax = steps.get(iMax);
  214.         double tMax = 0.5 * (sMax.getPreviousState().getTime() + sMax.getCurrentState().getTime());

  215.         // handle points outside of the integration interval
  216.         // or in the first and last step
  217.         if (locatePoint(time, sMin) <= 0) {
  218.             index = iMin;
  219.             return sMin.getInterpolatedState(time);
  220.         }
  221.         if (locatePoint(time, sMax) >= 0) {
  222.             index = iMax;
  223.             return sMax.getInterpolatedState(time);
  224.         }

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

  227.             // use the last estimated index as the splitting index
  228.             final ODEStateInterpolator si = steps.get(index);
  229.             final int location = locatePoint(time, si);
  230.             if (location < 0) {
  231.                 iMax = index;
  232.                 tMax = 0.5 * (si.getPreviousState().getTime() + si.getCurrentState().getTime());
  233.             } else if (location > 0) {
  234.                 iMin = index;
  235.                 tMin = 0.5 * (si.getPreviousState().getTime() + si.getCurrentState().getTime());
  236.             } else {
  237.                 // we have found the target step, no need to continue searching
  238.                 return si.getInterpolatedState(time);
  239.             }

  240.             // compute a new estimate of the index in the reduced table slice
  241.             final int iMed = (iMin + iMax) / 2;
  242.             final ODEStateInterpolator sMed = steps.get(iMed);
  243.             final double tMed = 0.5 * (sMed.getPreviousState().getTime() + sMed.getCurrentState().getTime());

  244.             if ((FastMath.abs(tMed - tMin) < 1e-6) || (FastMath.abs(tMax - tMed) < 1e-6)) {
  245.                 // too close to the bounds, we estimate using a simple dichotomy
  246.                 index = iMed;
  247.             } else {
  248.                 // estimate the index using a reverse quadratic polynom
  249.                 // (reverse means we have i = P(t), thus allowing to simply
  250.                 // compute index = P(time) rather than solving a quadratic equation)
  251.                 final double d12 = tMax - tMed;
  252.                 final double d23 = tMed - tMin;
  253.                 final double d13 = tMax - tMin;
  254.                 final double dt1 = time - tMax;
  255.                 final double dt2 = time - tMed;
  256.                 final double dt3 = time - tMin;
  257.                 final double iLagrange = ((dt2 * dt3 * d23) * iMax -
  258.                                 (dt1 * dt3 * d13) * iMed +
  259.                                 (dt1 * dt2 * d12) * iMin) /
  260.                                 (d12 * d23 * d13);
  261.                 index = (int) FastMath.rint(iLagrange);
  262.             }

  263.             // force the next size reduction to be at least one tenth
  264.             final int low  = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10);
  265.             final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
  266.             if (index < low) {
  267.                 index = low;
  268.             } else if (index > high) {
  269.                 index = high;
  270.             }

  271.         }

  272.         // now the table slice is very small, we perform an iterative search
  273.         index = iMin;
  274.         while ((index <= iMax) && (locatePoint(time, steps.get(index)) > 0)) {
  275.             ++index;
  276.         }

  277.         return steps.get(index).getInterpolatedState(time);

  278.     }

  279.     /** Compare a step interval and a double.
  280.      * @param time point to locate
  281.      * @param interval step interval
  282.      * @return -1 if the double is before the interval, 0 if it is in
  283.      * the interval, and +1 if it is after the interval, according to
  284.      * the interval direction
  285.      */
  286.     private int locatePoint(final double time, final ODEStateInterpolator interval) {
  287.         if (forward) {
  288.             if (time < interval.getPreviousState().getTime()) {
  289.                 return -1;
  290.             } else if (time > interval.getCurrentState().getTime()) {
  291.                 return +1;
  292.             } else {
  293.                 return 0;
  294.             }
  295.         }
  296.         if (time > interval.getPreviousState().getTime()) {
  297.             return -1;
  298.         } else if (time < interval.getCurrentState().getTime()) {
  299.             return +1;
  300.         } else {
  301.             return 0;
  302.         }
  303.     }

  304. }