FiniteDifferencesDifferentiator.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.analysis.differentiation;

  22. import java.io.Serializable;

  23. import org.hipparchus.analysis.UnivariateFunction;
  24. import org.hipparchus.analysis.UnivariateMatrixFunction;
  25. import org.hipparchus.analysis.UnivariateVectorFunction;
  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.exception.MathIllegalArgumentException;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathArrays;

  30. /** Univariate functions differentiator using finite differences.
  31.  * <p>
  32.  * This class creates some wrapper objects around regular
  33.  * {@link UnivariateFunction univariate functions} (or {@link
  34.  * UnivariateVectorFunction univariate vector functions} or {@link
  35.  * UnivariateMatrixFunction univariate matrix functions}). These
  36.  * wrapper objects compute derivatives in addition to function
  37.  * values.
  38.  * </p>
  39.  * <p>
  40.  * The wrapper objects work by calling the underlying function on
  41.  * a sampling grid around the current point and performing polynomial
  42.  * interpolation. A finite differences scheme with n points is
  43.  * theoretically able to compute derivatives up to order n-1, but
  44.  * it is generally better to have a slight margin. The step size must
  45.  * also be small enough in order for the polynomial approximation to
  46.  * be good in the current point neighborhood, but it should not be too
  47.  * small because numerical instability appears quickly (there are several
  48.  * differences of close points). Choosing the number of points and
  49.  * the step size is highly problem dependent.
  50.  * </p>
  51.  * <p>
  52.  * As an example of good and bad settings, lets consider the quintic
  53.  * polynomial function {@code f(x) = (x-1)*(x-0.5)*x*(x+0.5)*(x+1)}.
  54.  * Since it is a polynomial, finite differences with at least 6 points
  55.  * should theoretically recover the exact same polynomial and hence
  56.  * compute accurate derivatives for any order. However, due to numerical
  57.  * errors, we get the following results for a 7 points finite differences
  58.  * for abscissae in the [-10, 10] range:
  59.  * <ul>
  60.  *   <li>step size = 0.25, second order derivative error about 9.97e-10</li>
  61.  *   <li>step size = 0.25, fourth order derivative error about 5.43e-8</li>
  62.  *   <li>step size = 1.0e-6, second order derivative error about 148</li>
  63.  *   <li>step size = 1.0e-6, fourth order derivative error about 6.35e+14</li>
  64.  * </ul>
  65.  * <p>
  66.  * This example shows that the small step size is really bad, even simply
  67.  * for second order derivative!</p>
  68.  *
  69.  */
  70. public class FiniteDifferencesDifferentiator
  71.     implements UnivariateFunctionDifferentiator, UnivariateVectorFunctionDifferentiator,
  72.                UnivariateMatrixFunctionDifferentiator, Serializable {

  73.     /** Serializable UID. */
  74.     private static final long serialVersionUID = 20120917L;

  75.     /** Number of points to use. */
  76.     private final int nbPoints;

  77.     /** Step size. */
  78.     private final double stepSize;

  79.     /** Half sample span. */
  80.     private final double halfSampleSpan;

  81.     /** Lower bound for independent variable. */
  82.     private final double tMin;

  83.     /** Upper bound for independent variable. */
  84.     private final double tMax;

  85.     /**
  86.      * Build a differentiator with number of points and step size when independent variable is unbounded.
  87.      * <p>
  88.      * Beware that wrong settings for the finite differences differentiator
  89.      * can lead to highly unstable and inaccurate results, especially for
  90.      * high derivation orders. Using very small step sizes is often a
  91.      * <em>bad</em> idea.
  92.      * </p>
  93.      * @param nbPoints number of points to use
  94.      * @param stepSize step size (gap between each point)
  95.      * @exception MathIllegalArgumentException if {@code stepsize <= 0} (note that
  96.      * {@link MathIllegalArgumentException} extends {@link MathIllegalArgumentException})
  97.      * @exception MathIllegalArgumentException {@code nbPoint <= 1}
  98.      */
  99.     public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize)
  100.         throws MathIllegalArgumentException {
  101.         this(nbPoints, stepSize, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
  102.     }

  103.     /**
  104.      * Build a differentiator with number of points and step size when independent variable is bounded.
  105.      * <p>
  106.      * When the independent variable is bounded (tLower &lt; t &lt; tUpper), the sampling
  107.      * points used for differentiation will be adapted to ensure the constraint holds
  108.      * even near the boundaries. This means the sample will not be centered anymore in
  109.      * these cases. At an extreme case, computing derivatives exactly at the lower bound
  110.      * will lead the sample to be entirely on the right side of the derivation point.
  111.      * </p>
  112.      * <p>
  113.      * Note that the boundaries are considered to be excluded for function evaluation.
  114.      * </p>
  115.      * <p>
  116.      * Beware that wrong settings for the finite differences differentiator
  117.      * can lead to highly unstable and inaccurate results, especially for
  118.      * high derivation orders. Using very small step sizes is often a
  119.      * <em>bad</em> idea.
  120.      * </p>
  121.      * @param nbPoints number of points to use
  122.      * @param stepSize step size (gap between each point)
  123.      * @param tLower lower bound for independent variable (may be {@code Double.NEGATIVE_INFINITY}
  124.      * if there are no lower bounds)
  125.      * @param tUpper upper bound for independent variable (may be {@code Double.POSITIVE_INFINITY}
  126.      * if there are no upper bounds)
  127.      * @exception MathIllegalArgumentException if {@code stepsize <= 0} (note that
  128.      * {@link MathIllegalArgumentException} extends {@link MathIllegalArgumentException})
  129.      * @exception MathIllegalArgumentException {@code nbPoint <= 1}
  130.      * @exception MathIllegalArgumentException {@code stepSize * (nbPoints - 1) >= tUpper - tLower}
  131.      */
  132.     public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize,
  133.                                            final double tLower, final double tUpper)
  134.             throws MathIllegalArgumentException {

  135.         if (nbPoints <= 1) {
  136.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  137.                                                    stepSize, 1);
  138.         }
  139.         this.nbPoints = nbPoints;

  140.         if (stepSize <= 0) {
  141.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED,
  142.                                                    stepSize, 0);
  143.         }
  144.         this.stepSize = stepSize;

  145.         halfSampleSpan = 0.5 * stepSize * (nbPoints - 1);
  146.         if (2 * halfSampleSpan >= tUpper - tLower) {
  147.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
  148.                                                    2 * halfSampleSpan, tUpper - tLower);
  149.         }
  150.         final double safety = FastMath.ulp(halfSampleSpan);
  151.         this.tMin = tLower + halfSampleSpan + safety;
  152.         this.tMax = tUpper - halfSampleSpan - safety;

  153.     }

  154.     /**
  155.      * Get the number of points to use.
  156.      * @return number of points to use
  157.      */
  158.     public int getNbPoints() {
  159.         return nbPoints;
  160.     }

  161.     /**
  162.      * Get the step size.
  163.      * @return step size
  164.      */
  165.     public double getStepSize() {
  166.         return stepSize;
  167.     }

  168.     /**
  169.      * Evaluate derivatives from a sample.
  170.      * <p>
  171.      * Evaluation is done using divided differences.
  172.      * </p>
  173.      * @param t evaluation abscissa value and derivatives
  174.      * @param t0 first sample point abscissa
  175.      * @param y function values sample {@code y[i] = f(t[i]) = f(t0 + i * stepSize)}
  176.      * @param <T> the type of the field elements
  177.      * @return value and derivatives at {@code t}
  178.      * @exception MathIllegalArgumentException if the requested derivation order
  179.      * is larger or equal to the number of points
  180.      */
  181.     private <T extends Derivative<T>> T evaluate(final T t, final double t0, final double[] y)
  182.         throws MathIllegalArgumentException {

  183.         // create divided differences diagonal arrays
  184.         final double[] top    = new double[nbPoints];
  185.         final double[] bottom = new double[nbPoints];

  186.         for (int i = 0; i < nbPoints; ++i) {

  187.             // update the bottom diagonal of the divided differences array
  188.             bottom[i] = y[i];
  189.             for (int j = 1; j <= i; ++j) {
  190.                 bottom[i - j] = (bottom[i - j + 1] - bottom[i - j]) / (j * stepSize);
  191.             }

  192.             // update the top diagonal of the divided differences array
  193.             top[i] = bottom[0];

  194.         }

  195.         // evaluate interpolation polynomial (represented by top diagonal) at t
  196.         T interpolation = t.getField().getZero();
  197.         T monomial      = null;
  198.         for (int i = 0; i < nbPoints; ++i) {
  199.             if (i == 0) {
  200.                 // start with monomial(t) = 1
  201.                 monomial = t.getField().getOne();
  202.             } else {
  203.                 // monomial(t) = (t - t0) * (t - t1) * ... * (t - t(i-1))
  204.                 final T deltaX = t.subtract(t0 + (i - 1) * stepSize);
  205.                 monomial = monomial.multiply(deltaX);
  206.             }
  207.             interpolation = interpolation.add(monomial.multiply(top[i]));
  208.         }

  209.         return interpolation;

  210.     }

  211.     /** {@inheritDoc}
  212.      * <p>The returned object cannot compute derivatives to arbitrary orders. The
  213.      * value function will throw a {@link MathIllegalArgumentException} if the requested
  214.      * derivation order is larger or equal to the number of points.
  215.      * </p>
  216.      */
  217.     @Override
  218.     public UnivariateDifferentiableFunction differentiate(final UnivariateFunction function) {
  219.         return new UnivariateDifferentiableFunction() {

  220.             /** {@inheritDoc} */
  221.             @Override
  222.             public double value(final double x) throws MathIllegalArgumentException {
  223.                 return function.value(x);
  224.             }

  225.             /** {@inheritDoc} */
  226.             @Override
  227.             public <T extends Derivative<T>> T value(T t)
  228.                 throws MathIllegalArgumentException {

  229.                 // check we can achieve the requested derivation order with the sample
  230.                 if (t.getOrder() >= nbPoints) {
  231.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
  232.                                                            t.getOrder(), nbPoints);
  233.                 }

  234.                 // compute sample position, trying to be centered if possible
  235.                 final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;

  236.                 // compute sample points
  237.                 final double[] y = new double[nbPoints];
  238.                 for (int i = 0; i < nbPoints; ++i) {
  239.                     y[i] = function.value(t0 + i * stepSize);
  240.                 }

  241.                 // evaluate derivatives
  242.                 return evaluate(t, t0, y);

  243.             }

  244.         };
  245.     }

  246.     /** {@inheritDoc}
  247.      * <p>The returned object cannot compute derivatives to arbitrary orders. The
  248.      * value function will throw a {@link MathIllegalArgumentException} if the requested
  249.      * derivation order is larger or equal to the number of points.
  250.      * </p>
  251.      */
  252.     @Override
  253.     public UnivariateDifferentiableVectorFunction differentiate(final UnivariateVectorFunction function) {
  254.         return new UnivariateDifferentiableVectorFunction() {

  255.             /** {@inheritDoc} */
  256.             @Override
  257.             public double[]value(final double x) throws MathIllegalArgumentException {
  258.                 return function.value(x);
  259.             }

  260.             /** {@inheritDoc} */
  261.             @Override
  262.             public <T extends Derivative<T>> T[] value(T t)
  263.                 throws MathIllegalArgumentException {

  264.                 // check we can achieve the requested derivation order with the sample
  265.                 if (t.getOrder() >= nbPoints) {
  266.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
  267.                                                            t.getOrder(), nbPoints);
  268.                 }

  269.                 // compute sample position, trying to be centered if possible
  270.                 final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;

  271.                 // compute sample points
  272.                 double[][] y = null;
  273.                 for (int i = 0; i < nbPoints; ++i) {
  274.                     final double[] v = function.value(t0 + i * stepSize);
  275.                     if (i == 0) {
  276.                         y = new double[v.length][nbPoints];
  277.                     }
  278.                     for (int j = 0; j < v.length; ++j) {
  279.                         y[j][i] = v[j];
  280.                     }
  281.                 }

  282.                 // evaluate derivatives
  283.                 final T[] value = MathArrays.buildArray(t.getField(), y.length);
  284.                 for (int j = 0; j < value.length; ++j) {
  285.                     value[j] = evaluate(t, t0, y[j]);
  286.                 }

  287.                 return value;

  288.             }

  289.         };
  290.     }

  291.     /** {@inheritDoc}
  292.      * <p>The returned object cannot compute derivatives to arbitrary orders. The
  293.      * value function will throw a {@link MathIllegalArgumentException} if the requested
  294.      * derivation order is larger or equal to the number of points.
  295.      * </p>
  296.      */
  297.     @Override
  298.     public UnivariateDifferentiableMatrixFunction differentiate(final UnivariateMatrixFunction function) {
  299.         return new UnivariateDifferentiableMatrixFunction() {

  300.             /** {@inheritDoc} */
  301.             @Override
  302.             public double[][]  value(final double x) throws MathIllegalArgumentException {
  303.                 return function.value(x);
  304.             }

  305.             /** {@inheritDoc} */
  306.             @Override
  307.             public <T extends Derivative<T>> T[][] value(T t)
  308.                 throws MathIllegalArgumentException {

  309.                 // check we can achieve the requested derivation order with the sample
  310.                 if (t.getOrder() >= nbPoints) {
  311.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
  312.                                                            t.getOrder(), nbPoints);
  313.                 }

  314.                 // compute sample position, trying to be centered if possible
  315.                 final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;

  316.                 // compute sample points
  317.                 double[][][] y = null;
  318.                 for (int i = 0; i < nbPoints; ++i) {
  319.                     final double[][] v = function.value(t0 + i * stepSize);
  320.                     if (i == 0) {
  321.                         y = new double[v.length][v[0].length][nbPoints];
  322.                     }
  323.                     for (int j = 0; j < v.length; ++j) {
  324.                         for (int k = 0; k < v[j].length; ++k) {
  325.                             y[j][k][i] = v[j][k];
  326.                         }
  327.                     }
  328.                 }

  329.                 // evaluate derivatives
  330.                 final T[][] value = MathArrays.buildArray(t.getField(), y.length, y[0].length);
  331.                 for (int j = 0; j < value.length; ++j) {
  332.                     for (int k = 0; k < y[j].length; ++k) {
  333.                         value[j][k] = evaluate(t, t0, y[j][k]);
  334.                     }
  335.                 }

  336.                 return value;

  337.             }

  338.         };
  339.     }

  340. }