AdaptiveStepsizeFieldIntegrator.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.nonstiff;

  18. import org.apache.commons.math4.legacy.core.Field;
  19. import org.apache.commons.math4.legacy.core.RealFieldElement;
  20. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  21. import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
  22. import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
  23. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  24. import org.apache.commons.math4.legacy.ode.AbstractFieldIntegrator;
  25. import org.apache.commons.math4.legacy.ode.FieldEquationsMapper;
  26. import org.apache.commons.math4.legacy.ode.FieldODEState;
  27. import org.apache.commons.math4.legacy.ode.FieldODEStateAndDerivative;
  28. import org.apache.commons.math4.core.jdkmath.JdkMath;
  29. import org.apache.commons.math4.legacy.core.MathArrays;

  30. /**
  31.  * This abstract class holds the common part of all adaptive
  32.  * stepsize integrators for Ordinary Differential Equations.
  33.  *
  34.  * <p>These algorithms perform integration with stepsize control, which
  35.  * means the user does not specify the integration step but rather a
  36.  * tolerance on error. The error threshold is computed as
  37.  * <pre>
  38.  * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
  39.  * </pre>
  40.  * where absTol_i is the absolute tolerance for component i of the
  41.  * state vector and relTol_i is the relative tolerance for the same
  42.  * component. The user can also use only two scalar values absTol and
  43.  * relTol which will be used for all components.
  44.  *
  45.  * <p>
  46.  * Note that <em>only</em> the {@link FieldODEState#getState() main part}
  47.  * of the state vector is used for stepsize control. The {@link
  48.  * FieldODEState#getSecondaryState(int) secondary parts} of the state
  49.  * vector are explicitly ignored for stepsize control.
  50.  * </p>
  51.  *
  52.  * <p>If the estimated error for ym+1 is such that
  53.  * <pre>
  54.  * sqrt((sum (errEst_i / threshold_i)^2 ) / n) &lt; 1
  55.  * </pre>
  56.  *
  57.  * (where n is the main set dimension) then the step is accepted,
  58.  * otherwise the step is rejected and a new attempt is made with a new
  59.  * stepsize.
  60.  *
  61.  * @param <T> the type of the field elements
  62.  * @since 3.6
  63.  *
  64.  */

  65. public abstract class AdaptiveStepsizeFieldIntegrator<T extends RealFieldElement<T>>
  66.     extends AbstractFieldIntegrator<T> {

  67.     /** Allowed absolute scalar error. */
  68.     protected double scalAbsoluteTolerance;

  69.     /** Allowed relative scalar error. */
  70.     protected double scalRelativeTolerance;

  71.     /** Allowed absolute vectorial error. */
  72.     protected double[] vecAbsoluteTolerance;

  73.     /** Allowed relative vectorial error. */
  74.     protected double[] vecRelativeTolerance;

  75.     /** Main set dimension. */
  76.     protected int mainSetDimension;

  77.     /** User supplied initial step. */
  78.     private T initialStep;

  79.     /** Minimal step. */
  80.     private T minStep;

  81.     /** Maximal step. */
  82.     private T maxStep;

  83.     /** Build an integrator with the given stepsize bounds.
  84.      * The default step handler does nothing.
  85.      * @param field field to which the time and state vector elements belong
  86.      * @param name name of the method
  87.      * @param minStep minimal step (sign is irrelevant, regardless of
  88.      * integration direction, forward or backward), the last step can
  89.      * be smaller than this
  90.      * @param maxStep maximal step (sign is irrelevant, regardless of
  91.      * integration direction, forward or backward), the last step can
  92.      * be smaller than this
  93.      * @param scalAbsoluteTolerance allowed absolute error
  94.      * @param scalRelativeTolerance allowed relative error
  95.      */
  96.     public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name,
  97.                                            final double minStep, final double maxStep,
  98.                                            final double scalAbsoluteTolerance,
  99.                                            final double scalRelativeTolerance) {

  100.         super(field, name);
  101.         setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
  102.         resetInternalState();
  103.     }

  104.     /** Build an integrator with the given stepsize bounds.
  105.      * The default step handler does nothing.
  106.      * @param field field to which the time and state vector elements belong
  107.      * @param name name of the method
  108.      * @param minStep minimal step (sign is irrelevant, regardless of
  109.      * integration direction, forward or backward), the last step can
  110.      * be smaller than this
  111.      * @param maxStep maximal step (sign is irrelevant, regardless of
  112.      * integration direction, forward or backward), the last step can
  113.      * be smaller than this
  114.      * @param vecAbsoluteTolerance allowed absolute error
  115.      * @param vecRelativeTolerance allowed relative error
  116.      */
  117.     public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name,
  118.                                            final double minStep, final double maxStep,
  119.                                            final double[] vecAbsoluteTolerance,
  120.                                            final double[] vecRelativeTolerance) {

  121.         super(field, name);
  122.         setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
  123.         resetInternalState();
  124.     }

  125.     /** Set the adaptive step size control parameters.
  126.      * <p>
  127.      * A side effect of this method is to also reset the initial
  128.      * step so it will be automatically computed by the integrator
  129.      * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize}
  130.      * is not called by the user.
  131.      * </p>
  132.      * @param minimalStep minimal step (must be positive even for backward
  133.      * integration), the last step can be smaller than this
  134.      * @param maximalStep maximal step (must be positive even for backward
  135.      * integration)
  136.      * @param absoluteTolerance allowed absolute error
  137.      * @param relativeTolerance allowed relative error
  138.      */
  139.     public void setStepSizeControl(final double minimalStep, final double maximalStep,
  140.                                    final double absoluteTolerance,
  141.                                    final double relativeTolerance) {

  142.         minStep     = getField().getZero().add(JdkMath.abs(minimalStep));
  143.         maxStep     = getField().getZero().add(JdkMath.abs(maximalStep));
  144.         initialStep = getField().getOne().negate();

  145.         scalAbsoluteTolerance = absoluteTolerance;
  146.         scalRelativeTolerance = relativeTolerance;
  147.         vecAbsoluteTolerance  = null;
  148.         vecRelativeTolerance  = null;
  149.     }

  150.     /** Set the adaptive step size control parameters.
  151.      * <p>
  152.      * A side effect of this method is to also reset the initial
  153.      * step so it will be automatically computed by the integrator
  154.      * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize}
  155.      * is not called by the user.
  156.      * </p>
  157.      * @param minimalStep minimal step (must be positive even for backward
  158.      * integration), the last step can be smaller than this
  159.      * @param maximalStep maximal step (must be positive even for backward
  160.      * integration)
  161.      * @param absoluteTolerance allowed absolute error
  162.      * @param relativeTolerance allowed relative error
  163.      */
  164.     public void setStepSizeControl(final double minimalStep, final double maximalStep,
  165.                                    final double[] absoluteTolerance,
  166.                                    final double[] relativeTolerance) {

  167.         minStep     = getField().getZero().add(JdkMath.abs(minimalStep));
  168.         maxStep     = getField().getZero().add(JdkMath.abs(maximalStep));
  169.         initialStep = getField().getOne().negate();

  170.         scalAbsoluteTolerance = 0;
  171.         scalRelativeTolerance = 0;
  172.         vecAbsoluteTolerance  = absoluteTolerance.clone();
  173.         vecRelativeTolerance  = relativeTolerance.clone();
  174.     }

  175.     /** Set the initial step size.
  176.      * <p>This method allows the user to specify an initial positive
  177.      * step size instead of letting the integrator guess it by
  178.      * itself. If this method is not called before integration is
  179.      * started, the initial step size will be estimated by the
  180.      * integrator.</p>
  181.      * @param initialStepSize initial step size to use (must be positive even
  182.      * for backward integration ; providing a negative value or a value
  183.      * outside of the min/max step interval will lead the integrator to
  184.      * ignore the value and compute the initial step size by itself)
  185.      */
  186.     public void setInitialStepSize(final T initialStepSize) {
  187.         if (initialStepSize.subtract(minStep).getReal() < 0 ||
  188.             initialStepSize.subtract(maxStep).getReal() > 0) {
  189.             initialStep = getField().getOne().negate();
  190.         } else {
  191.             initialStep = initialStepSize;
  192.         }
  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     protected void sanityChecks(final FieldODEState<T> eqn, final T t)
  197.         throws DimensionMismatchException, NumberIsTooSmallException {

  198.         super.sanityChecks(eqn, t);

  199.         mainSetDimension = eqn.getStateDimension();

  200.         if (vecAbsoluteTolerance != null && vecAbsoluteTolerance.length != mainSetDimension) {
  201.             throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length);
  202.         }

  203.         if (vecRelativeTolerance != null && vecRelativeTolerance.length != mainSetDimension) {
  204.             throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length);
  205.         }
  206.     }

  207.     /** Initialize the integration step.
  208.      * @param forward forward integration indicator
  209.      * @param order order of the method
  210.      * @param scale scaling vector for the state vector (can be shorter than state vector)
  211.      * @param state0 state at integration start time
  212.      * @param mapper mapper for all the equations
  213.      * @return first integration step
  214.      * @exception MaxCountExceededException if the number of functions evaluations is exceeded
  215.      * @exception DimensionMismatchException if arrays dimensions do not match equations settings
  216.      */
  217.     public T initializeStep(final boolean forward, final int order, final T[] scale,
  218.                             final FieldODEStateAndDerivative<T> state0,
  219.                             final FieldEquationsMapper<T> mapper)
  220.         throws MaxCountExceededException, DimensionMismatchException {

  221.         if (initialStep.getReal() > 0) {
  222.             // use the user provided value
  223.             return forward ? initialStep : initialStep.negate();
  224.         }

  225.         // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale||
  226.         // this guess will be used to perform an Euler step
  227.         final T[] y0    = mapper.mapState(state0);
  228.         final T[] yDot0 = mapper.mapDerivative(state0);
  229.         T yOnScale2    = getField().getZero();
  230.         T yDotOnScale2 = getField().getZero();
  231.         for (int j = 0; j < scale.length; ++j) {
  232.             final T ratio    = y0[j].divide(scale[j]);
  233.             yOnScale2        = yOnScale2.add(ratio.multiply(ratio));
  234.             final T ratioDot = yDot0[j].divide(scale[j]);
  235.             yDotOnScale2     = yDotOnScale2.add(ratioDot.multiply(ratioDot));
  236.         }

  237.         T h = (yOnScale2.getReal() < 1.0e-10 || yDotOnScale2.getReal() < 1.0e-10) ?
  238.               getField().getZero().add(1.0e-6) :
  239.               yOnScale2.divide(yDotOnScale2).sqrt().multiply(0.01);
  240.         if (! forward) {
  241.             h = h.negate();
  242.         }

  243.         // perform an Euler step using the preceding rough guess
  244.         final T[] y1 = MathArrays.buildArray(getField(), y0.length);
  245.         for (int j = 0; j < y0.length; ++j) {
  246.             y1[j] = y0[j].add(yDot0[j].multiply(h));
  247.         }
  248.         final T[] yDot1 = computeDerivatives(state0.getTime().add(h), y1);

  249.         // estimate the second derivative of the solution
  250.         T yDDotOnScale = getField().getZero();
  251.         for (int j = 0; j < scale.length; ++j) {
  252.             final T ratioDotDot = yDot1[j].subtract(yDot0[j]).divide(scale[j]);
  253.             yDDotOnScale = yDDotOnScale.add(ratioDotDot.multiply(ratioDotDot));
  254.         }
  255.         yDDotOnScale = yDDotOnScale.sqrt().divide(h);

  256.         // step size is computed such that
  257.         // h^order * max (||y'/tol||, ||y''/tol||) = 0.01
  258.         final T maxInv2 = RealFieldElement.max(yDotOnScale2.sqrt(), yDDotOnScale);
  259.         final T h1 = maxInv2.getReal() < 1.0e-15 ?
  260.                      RealFieldElement.max(getField().getZero().add(1.0e-6), h.abs().multiply(0.001)) :
  261.                      maxInv2.multiply(100).reciprocal().pow(1.0 / order);
  262.         h = RealFieldElement.min(h.abs().multiply(100), h1);
  263.         h = RealFieldElement.max(h, state0.getTime().abs().multiply(1.0e-12));  // avoids cancellation when computing t1 - t0
  264.         h = RealFieldElement.max(minStep, RealFieldElement.min(maxStep, h));
  265.         if (! forward) {
  266.             h = h.negate();
  267.         }

  268.         return h;
  269.     }

  270.     /** Filter the integration step.
  271.      * @param h signed step
  272.      * @param forward forward integration indicator
  273.      * @param acceptSmall if true, steps smaller than the minimal value
  274.      * are silently increased up to this value, if false such small
  275.      * steps generate an exception
  276.      * @return a bounded integration step (h if no bound is reach, or a bounded value)
  277.      * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false
  278.      */
  279.     protected T filterStep(final T h, final boolean forward, final boolean acceptSmall)
  280.         throws NumberIsTooSmallException {

  281.         T filteredH = h;
  282.         if (h.abs().subtract(minStep).getReal() < 0) {
  283.             if (acceptSmall) {
  284.                 filteredH = forward ? minStep : minStep.negate();
  285.             } else {
  286.                 throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION,
  287.                                                     h.abs().getReal(), minStep.getReal(), true);
  288.             }
  289.         }

  290.         if (filteredH.subtract(maxStep).getReal() > 0) {
  291.             filteredH = maxStep;
  292.         } else if (filteredH.add(maxStep).getReal() < 0) {
  293.             filteredH = maxStep.negate();
  294.         }

  295.         return filteredH;
  296.     }

  297.     /** Reset internal state to dummy values. */
  298.     protected void resetInternalState() {
  299.         setStepStart(null);
  300.         setStepSize(minStep.multiply(maxStep).sqrt());
  301.     }

  302.     /** Get the minimal step.
  303.      * @return minimal step
  304.      */
  305.     public T getMinStep() {
  306.         return minStep;
  307.     }

  308.     /** Get the maximal step.
  309.      * @return maximal step
  310.      */
  311.     public T getMaxStep() {
  312.         return maxStep;
  313.     }
  314. }