FieldDenseOutputModel.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.util.ArrayList;
  23. import java.util.List;

  24. import org.hipparchus.CalculusFieldElement;
  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.exception.MathIllegalArgumentException;
  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.ode.sampling.FieldODEStepHandler;
  29. import org.hipparchus.ode.sampling.FieldODEStateInterpolator;
  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(CalculusFieldElement) getInterpolatedState}
  41.  * method to retrieve this information at any time. It is important to wait
  42.  * for the integration to be over before attempting to call {@link
  43.  * #getInterpolatedState(CalculusFieldElement)} because some internal
  44.  * variables are set only once the last step has been handled.</p>
  45.  *
  46.  * <p>This is useful for example if the main loop of the user
  47.  * application should remain independent from the integration process
  48.  * or if one needs to mimic the behaviour of an analytical model
  49.  * despite a numerical model is used (i.e. one needs the ability to
  50.  * get the model value at any time or to navigate through the
  51.  * data).</p>
  52.  *
  53.  * <p>If problem modeling is done with several separate
  54.  * integration phases for contiguous intervals, the same
  55.  * FieldDenseOutputModel can be used as step handler for all
  56.  * integration phases as long as they are performed in order and in
  57.  * the same direction. As an example, one can extrapolate the
  58.  * trajectory of a satellite with one model (i.e. one set of
  59.  * differential equations) up to the beginning of a maneuver, use
  60.  * another more complex model including thrusters modeling and
  61.  * accurate attitude control during the maneuver, and revert to the
  62.  * first model after the end of the maneuver. If the same continuous
  63.  * output model handles the steps of all integration phases, the user
  64.  * do not need to bother when the maneuver begins or ends, he has all
  65.  * the data available in a transparent manner.</p>
  66.  *
  67.  * <p>One should be aware that the amount of data stored in a
  68.  * FieldDenseOutputModel instance can be important if the state vector
  69.  * is large, if the integration interval is long or if the steps are
  70.  * small (which can result from small tolerance settings in {@link
  71.  * org.hipparchus.ode.nonstiff.AdaptiveStepsizeFieldIntegrator adaptive
  72.  * step size integrators}).</p>
  73.  *
  74.  * @see FieldODEStepHandler
  75.  * @see FieldODEStateInterpolator
  76.  * @param <T> the type of the field elements
  77.  */

  78. public class FieldDenseOutputModel<T extends CalculusFieldElement<T>>
  79.     implements FieldODEStepHandler<T> {

  80.     /** Initial integration time. */
  81.     private T initialTime;

  82.     /** Final integration time. */
  83.     private T finalTime;

  84.     /** Integration direction indicator. */
  85.     private boolean forward;

  86.     /** Current interpolator index. */
  87.     private int index;

  88.     /** Steps table. */
  89.     private List<FieldODEStateInterpolator<T>> steps;

  90.     /** Simple constructor.
  91.      * Build an empty continuous output model.
  92.      */
  93.     public FieldDenseOutputModel() {
  94.         steps       = new ArrayList<>();
  95.         initialTime = null;
  96.         finalTime   = null;
  97.         forward     = true;
  98.         index       = 0;
  99.     }

  100.     /** Append another model at the end of the instance.
  101.      * @param model model to add at the end of the instance
  102.      * @exception MathIllegalArgumentException if the model to append is not
  103.      * compatible with the instance (dimension of the state vector,
  104.      * propagation direction, hole between the dates)
  105.      * @exception MathIllegalArgumentException if the dimensions of the states or
  106.      * the number of secondary states do not match
  107.      * @exception MathIllegalStateException if the number of functions evaluations is exceeded
  108.      * during step finalization
  109.      */
  110.     public void append(final FieldDenseOutputModel<T> model)
  111.         throws MathIllegalArgumentException, MathIllegalStateException {

  112.         if (model.steps.isEmpty()) {
  113.             return;
  114.         }

  115.         if (steps.isEmpty()) {
  116.             initialTime = model.initialTime;
  117.             forward     = model.forward;
  118.         } else {

  119.             // safety checks
  120.             final FieldODEStateAndDerivative<T> s1 = steps.get(0).getPreviousState();
  121.             final FieldODEStateAndDerivative<T> s2 = model.steps.get(0).getPreviousState();
  122.             checkDimensionsEquality(s1.getPrimaryStateDimension(), s2.getPrimaryStateDimension());
  123.             checkDimensionsEquality(s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates());
  124.             for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) {
  125.                 checkDimensionsEquality(s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i));
  126.             }

  127.             if (forward ^ model.forward) {
  128.                 throw new MathIllegalArgumentException(LocalizedODEFormats.PROPAGATION_DIRECTION_MISMATCH);
  129.             }

  130.             final FieldODEStateInterpolator<T> lastInterpolator = steps.get(index);
  131.             final T current  = lastInterpolator.getCurrentState().getTime();
  132.             final T previous = lastInterpolator.getPreviousState().getTime();
  133.             final T step = current.subtract(previous);
  134.             final T gap = model.getInitialTime().subtract(current);
  135.             if (gap.abs().subtract(step.abs().multiply(1.0e-3)).getReal() > 0) {
  136.                 throw new MathIllegalArgumentException(LocalizedODEFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
  137.                                                        gap.norm());
  138.             }

  139.         }

  140.         for (FieldODEStateInterpolator<T> interpolator : model.steps) {
  141.             steps.add(interpolator);
  142.         }

  143.         index = steps.size() - 1;
  144.         finalTime = (steps.get(index)).getCurrentState().getTime();

  145.     }

  146.     /** Check dimensions equality.
  147.      * @param d1 first dimension
  148.      * @param d2 second dimansion
  149.      * @exception MathIllegalArgumentException if dimensions do not match
  150.      */
  151.     private void checkDimensionsEquality(final int d1, final int d2)
  152.         throws MathIllegalArgumentException {
  153.         if (d1 != d2) {
  154.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  155.                                                    d2, d1);
  156.         }
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public void init(final FieldODEStateAndDerivative<T> initialState, final T t) {
  161.         initialTime = initialState.getTime();
  162.         finalTime   = t;
  163.         forward     = true;
  164.         index       = 0;
  165.         steps.clear();
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public void handleStep(final FieldODEStateInterpolator<T> interpolator) {

  170.         if (steps.isEmpty()) {
  171.             initialTime = interpolator.getPreviousState().getTime();
  172.             forward     = interpolator.isForward();
  173.         }

  174.         steps.add(interpolator);
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     public void finish(FieldODEStateAndDerivative<T> finalState) {
  179.         finalTime = finalState.getTime();
  180.         index     = steps.size() - 1;
  181.     }

  182.     /**
  183.      * Get the initial integration time.
  184.      * @return initial integration time
  185.      */
  186.     public T getInitialTime() {
  187.         return initialTime;
  188.     }

  189.     /**
  190.      * Get the final integration time.
  191.      * @return final integration time
  192.      */
  193.     public T getFinalTime() {
  194.         return finalTime;
  195.     }

  196.     /**
  197.      * Get the state at interpolated time.
  198.      * @param time time of the interpolated point
  199.      * @return state at interpolated time
  200.      */
  201.     public FieldODEStateAndDerivative<T> getInterpolatedState(final T time) {

  202.         // initialize the search with the complete steps table
  203.         int iMin = 0;
  204.         final FieldODEStateInterpolator<T> sMin = steps.get(iMin);
  205.         T tMin = sMin.getPreviousState().getTime().add(sMin.getCurrentState().getTime()).multiply(0.5);

  206.         int iMax = steps.size() - 1;
  207.         final FieldODEStateInterpolator<T> sMax = steps.get(iMax);
  208.         T tMax = sMax.getPreviousState().getTime().add(sMax.getCurrentState().getTime()).multiply(0.5);

  209.         // handle points outside of the integration interval
  210.         // or in the first and last step
  211.         if (locatePoint(time, sMin) <= 0) {
  212.             index = iMin;
  213.             return sMin.getInterpolatedState(time);
  214.         }
  215.         if (locatePoint(time, sMax) >= 0) {
  216.             index = iMax;
  217.             return sMax.getInterpolatedState(time);
  218.         }

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

  221.             // use the last estimated index as the splitting index
  222.             final FieldODEStateInterpolator<T> si = steps.get(index);
  223.             final int location = locatePoint(time, si);
  224.             if (location < 0) {
  225.                 iMax = index;
  226.                 tMax = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5);
  227.             } else if (location > 0) {
  228.                 iMin = index;
  229.                 tMin = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5);
  230.             } else {
  231.                 // we have found the target step, no need to continue searching
  232.                 return si.getInterpolatedState(time);
  233.             }

  234.             // compute a new estimate of the index in the reduced table slice
  235.             final int iMed = (iMin + iMax) / 2;
  236.             final FieldODEStateInterpolator<T> sMed = steps.get(iMed);
  237.             final T tMed = sMed.getPreviousState().getTime().add(sMed.getCurrentState().getTime()).multiply(0.5);

  238.             if (tMed.subtract(tMin).abs().subtract(1.0e-6).getReal() < 0 ||
  239.                 tMax.subtract(tMed).abs().subtract(1.0e-6).getReal() < 0) {
  240.                 // too close to the bounds, we estimate using a simple dichotomy
  241.                 index = iMed;
  242.             } else {
  243.                 // estimate the index using a reverse quadratic polynomial
  244.                 // (reverse means we have i = P(t), thus allowing to simply
  245.                 // compute index = P(time) rather than solving a quadratic equation)
  246.                 final T d12 = tMax.subtract(tMed);
  247.                 final T d23 = tMed.subtract(tMin);
  248.                 final T d13 = tMax.subtract(tMin);
  249.                 final T dt1 = time.subtract(tMax);
  250.                 final T dt2 = time.subtract(tMed);
  251.                 final T dt3 = time.subtract(tMin);
  252.                 final T iLagrange =           dt2.multiply(dt3).multiply(d23).multiply(iMax).
  253.                                      subtract(dt1.multiply(dt3).multiply(d13).multiply(iMed)).
  254.                                      add(     dt1.multiply(dt2).multiply(d12).multiply(iMin)).
  255.                                      divide(d12.multiply(d23).multiply(d13));
  256.                 index = (int) FastMath.rint(iLagrange.getReal());
  257.             }

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

  266.         }

  267.         // now the table slice is very small, we perform an iterative search
  268.         index = iMin;
  269.         while (index <= iMax && locatePoint(time, steps.get(index)) > 0) {
  270.             ++index;
  271.         }

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

  273.     }

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

  299. }