ContinuousOutputFieldModel.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.util.ArrayList;
  19. import java.util.List;

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

  76. public class ContinuousOutputFieldModel<T extends RealFieldElement<T>>
  77.     implements FieldStepHandler<T> {

  78.     /** Initial integration time. */
  79.     private T initialTime;

  80.     /** Final integration time. */
  81.     private T finalTime;

  82.     /** Integration direction indicator. */
  83.     private boolean forward;

  84.     /** Current interpolator index. */
  85.     private int index;

  86.     /** Steps table. */
  87.     private List<FieldStepInterpolator<T>> steps;

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

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

  110.         if (model.steps.isEmpty()) {
  111.             return;
  112.         }

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

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

  125.             if (forward ^ model.forward) {
  126.                 throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
  127.             }

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

  138.         steps.addAll(model.steps);

  139.         index = steps.size() - 1;
  140.         finalTime = (steps.get(index)).getCurrentState().getTime();
  141.     }

  142.     /** Check dimensions equality.
  143.      * @param d1 first dimension
  144.      * @param d2 second dimension
  145.      * @exception DimensionMismatchException if dimensions do not match
  146.      */
  147.     private void checkDimensionsEquality(final int d1, final int d2)
  148.         throws DimensionMismatchException {
  149.         if (d1 != d2) {
  150.             throw new DimensionMismatchException(d2, d1);
  151.         }
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public void init(final FieldODEStateAndDerivative<T> initialState, final T t) {
  156.         initialTime = initialState.getTime();
  157.         finalTime   = t;
  158.         forward     = true;
  159.         index       = 0;
  160.         steps.clear();
  161.     }

  162.     /** Handle the last accepted step.
  163.      * A copy of the information provided by the last step is stored in
  164.      * the instance for later use.
  165.      * @param interpolator interpolator for the last accepted step.
  166.      * @param isLast true if the step is the last one
  167.      * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  168.      * during step finalization
  169.      */
  170.     @Override
  171.     public void handleStep(final FieldStepInterpolator<T> interpolator, final boolean isLast)
  172.         throws MaxCountExceededException {

  173.         if (steps.isEmpty()) {
  174.             initialTime = interpolator.getPreviousState().getTime();
  175.             forward     = interpolator.isForward();
  176.         }

  177.         steps.add(interpolator);

  178.         if (isLast) {
  179.             finalTime = interpolator.getCurrentState().getTime();
  180.             index     = steps.size() - 1;
  181.         }
  182.     }

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

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

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

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

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

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

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

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

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

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

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

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

  273.         return steps.get(index).getInterpolatedState(time);
  274.     }

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