View Javadoc
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.analysis.differentiation;
18  
19  import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
20  import org.apache.commons.math4.legacy.analysis.UnivariateMatrixFunction;
21  import org.apache.commons.math4.legacy.analysis.UnivariateVectorFunction;
22  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
23  import org.apache.commons.math4.legacy.exception.NotPositiveException;
24  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
25  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
26  import org.apache.commons.math4.core.jdkmath.JdkMath;
27  
28  /** Univariate functions differentiator using finite differences.
29   * <p>
30   * This class creates some wrapper objects around regular
31   * {@link UnivariateFunction univariate functions} (or {@link
32   * UnivariateVectorFunction univariate vector functions} or {@link
33   * UnivariateMatrixFunction univariate matrix functions}). These
34   * wrapper objects compute derivatives in addition to function
35   * values.
36   * </p>
37   * <p>
38   * The wrapper objects work by calling the underlying function on
39   * a sampling grid around the current point and performing polynomial
40   * interpolation. A finite differences scheme with n points is
41   * theoretically able to compute derivatives up to order n-1, but
42   * it is generally better to have a slight margin. The step size must
43   * also be small enough in order for the polynomial approximation to
44   * be good in the current point neighborhood, but it should not be too
45   * small because numerical instability appears quickly (there are several
46   * differences of close points). Choosing the number of points and
47   * the step size is highly problem dependent.
48   * </p>
49   * <p>
50   * As an example of good and bad settings, lets consider the quintic
51   * polynomial function {@code f(x) = (x-1)*(x-0.5)*x*(x+0.5)*(x+1)}.
52   * Since it is a polynomial, finite differences with at least 6 points
53   * should theoretically recover the exact same polynomial and hence
54   * compute accurate derivatives for any order. However, due to numerical
55   * errors, we get the following results for a 7 points finite differences
56   * for abscissae in the [-10, 10] range:
57   * <ul>
58   *   <li>step size = 0.25, second order derivative error about 9.97e-10</li>
59   *   <li>step size = 0.25, fourth order derivative error about 5.43e-8</li>
60   *   <li>step size = 1.0e-6, second order derivative error about 148</li>
61   *   <li>step size = 1.0e-6, fourth order derivative error about 6.35e+14</li>
62   * </ul>
63   * <p>
64   * This example shows that the small step size is really bad, even simply
65   * for second order derivative!</p>
66   *
67   * @since 3.1
68   */
69  public class FiniteDifferencesDifferentiator
70      implements UnivariateFunctionDifferentiator,
71                 UnivariateVectorFunctionDifferentiator,
72                 UnivariateMatrixFunctionDifferentiator {
73      /** Number of points to use. */
74      private final int nbPoints;
75  
76      /** Step size. */
77      private final double stepSize;
78  
79      /** Half sample span. */
80      private final double halfSampleSpan;
81  
82      /** Lower bound for independent variable. */
83      private final double tMin;
84  
85      /** Upper bound for independent variable. */
86      private final double tMax;
87  
88      /**
89       * Build a differentiator with number of points and step size when independent variable is unbounded.
90       * <p>
91       * Beware that wrong settings for the finite differences differentiator
92       * can lead to highly unstable and inaccurate results, especially for
93       * high derivation orders. Using very small step sizes is often a
94       * <em>bad</em> idea.
95       * </p>
96       * @param nbPoints number of points to use
97       * @param stepSize step size (gap between each point)
98       * @exception NotPositiveException if {@code stepsize <= 0} (note that
99       * {@link NotPositiveException} extends {@link NumberIsTooSmallException})
100      * @exception NumberIsTooSmallException {@code nbPoint <= 1}
101      */
102     public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize)
103         throws NotPositiveException, NumberIsTooSmallException {
104         this(nbPoints, stepSize, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
105     }
106 
107     /**
108      * Build a differentiator with number of points and step size when independent variable is bounded.
109      * <p>
110      * When the independent variable is bounded (tLower &lt; t &lt; tUpper), the sampling
111      * points used for differentiation will be adapted to ensure the constraint holds
112      * even near the boundaries. This means the sample will not be centered anymore in
113      * these cases. At an extreme case, computing derivatives exactly at the lower bound
114      * will lead the sample to be entirely on the right side of the derivation point.
115      * </p>
116      * <p>
117      * Note that the boundaries are considered to be excluded for function evaluation.
118      * </p>
119      * <p>
120      * Beware that wrong settings for the finite differences differentiator
121      * can lead to highly unstable and inaccurate results, especially for
122      * high derivation orders. Using very small step sizes is often a
123      * <em>bad</em> idea.
124      * </p>
125      * @param nbPoints number of points to use
126      * @param stepSize step size (gap between each point)
127      * @param tLower lower bound for independent variable (may be {@code Double.NEGATIVE_INFINITY}
128      * if there are no lower bounds)
129      * @param tUpper upper bound for independent variable (may be {@code Double.POSITIVE_INFINITY}
130      * if there are no upper bounds)
131      * @exception NotPositiveException if {@code stepsize <= 0} (note that
132      * {@link NotPositiveException} extends {@link NumberIsTooSmallException})
133      * @exception NumberIsTooSmallException {@code nbPoint <= 1}
134      * @exception NumberIsTooLargeException {@code stepSize * (nbPoints - 1) >= tUpper - tLower}
135      */
136     public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize,
137                                            final double tLower, final double tUpper)
138             throws NotPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
139 
140         if (nbPoints <= 1) {
141             throw new NumberIsTooSmallException(stepSize, 1, false);
142         }
143         this.nbPoints = nbPoints;
144 
145         if (stepSize <= 0) {
146             throw new NotPositiveException(stepSize);
147         }
148         this.stepSize = stepSize;
149 
150         halfSampleSpan = 0.5 * stepSize * (nbPoints - 1);
151         if (2 * halfSampleSpan >= tUpper - tLower) {
152             throw new NumberIsTooLargeException(2 * halfSampleSpan, tUpper - tLower, false);
153         }
154         final double safety = JdkMath.ulp(halfSampleSpan);
155         this.tMin = tLower + halfSampleSpan + safety;
156         this.tMax = tUpper - halfSampleSpan - safety;
157     }
158 
159     /**
160      * Get the number of points to use.
161      * @return number of points to use
162      */
163     public int getNbPoints() {
164         return nbPoints;
165     }
166 
167     /**
168      * Get the step size.
169      * @return step size
170      */
171     public double getStepSize() {
172         return stepSize;
173     }
174 
175     /**
176      * Evaluate derivatives from a sample.
177      * <p>
178      * Evaluation is done using divided differences.
179      * </p>
180      * @param t evaluation abscissa value and derivatives
181      * @param t0 first sample point abscissa
182      * @param y function values sample {@code y[i] = f(t[i]) = f(t0 + i * stepSize)}
183      * @return value and derivatives at {@code t}
184      * @exception NumberIsTooLargeException if the requested derivation order
185      * is larger or equal to the number of points
186      */
187     private DerivativeStructure evaluate(final DerivativeStructure t, final double t0,
188                                          final double[] y)
189         throws NumberIsTooLargeException {
190 
191         // create divided differences diagonal arrays
192         final double[] top    = new double[nbPoints];
193         final double[] bottom = new double[nbPoints];
194 
195         for (int i = 0; i < nbPoints; ++i) {
196 
197             // update the bottom diagonal of the divided differences array
198             bottom[i] = y[i];
199             for (int j = 1; j <= i; ++j) {
200                 bottom[i - j] = (bottom[i - j + 1] - bottom[i - j]) / (j * stepSize);
201             }
202 
203             // update the top diagonal of the divided differences array
204             top[i] = bottom[0];
205         }
206 
207         // evaluate interpolation polynomial (represented by top diagonal) at t
208         final int order            = t.getOrder();
209         final int parameters       = t.getFreeParameters();
210         final double[] derivatives = t.getAllDerivatives();
211         final double dt0           = t.getValue() - t0;
212         DerivativeStructure interpolation = new DerivativeStructure(parameters, order, 0.0);
213         DerivativeStructure monomial = null;
214         for (int i = 0; i < nbPoints; ++i) {
215             if (i == 0) {
216                 // start with monomial(t) = 1
217                 monomial = new DerivativeStructure(parameters, order, 1.0);
218             } else {
219                 // monomial(t) = (t - t0) * (t - t1) * ... * (t - t(i-1))
220                 derivatives[0] = dt0 - (i - 1) * stepSize;
221                 final DerivativeStructure deltaX = new DerivativeStructure(parameters, order, derivatives);
222                 monomial = monomial.multiply(deltaX);
223             }
224             interpolation = interpolation.add(monomial.multiply(top[i]));
225         }
226 
227         return interpolation;
228     }
229 
230     /** {@inheritDoc}
231      * <p>The returned object cannot compute derivatives to arbitrary orders. The
232      * value function will throw a {@link NumberIsTooLargeException} if the requested
233      * derivation order is larger or equal to the number of points.
234      * </p>
235      */
236     @Override
237     public UnivariateDifferentiableFunction differentiate(final UnivariateFunction function) {
238         return new UnivariateDifferentiableFunction() {
239 
240             /** {@inheritDoc} */
241             @Override
242             public double value(final double x) throws MathIllegalArgumentException {
243                 return function.value(x);
244             }
245 
246             /** {@inheritDoc} */
247             @Override
248             public DerivativeStructure value(final DerivativeStructure t)
249                 throws MathIllegalArgumentException {
250 
251                 // check we can achieve the requested derivation order with the sample
252                 if (t.getOrder() >= nbPoints) {
253                     throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
254                 }
255 
256                 // compute sample position, trying to be centered if possible
257                 final double t0 = JdkMath.max(JdkMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
258 
259                 // compute sample points
260                 final double[] y = new double[nbPoints];
261                 for (int i = 0; i < nbPoints; ++i) {
262                     y[i] = function.value(t0 + i * stepSize);
263                 }
264 
265                 // evaluate derivatives
266                 return evaluate(t, t0, y);
267             }
268         };
269     }
270 
271     /** {@inheritDoc}
272      * <p>The returned object cannot compute derivatives to arbitrary orders. The
273      * value function will throw a {@link NumberIsTooLargeException} if the requested
274      * derivation order is larger or equal to the number of points.
275      * </p>
276      */
277     @Override
278     public UnivariateDifferentiableVectorFunction differentiate(final UnivariateVectorFunction function) {
279         return new UnivariateDifferentiableVectorFunction() {
280 
281             /** {@inheritDoc} */
282             @Override
283             public double[]value(final double x) throws MathIllegalArgumentException {
284                 return function.value(x);
285             }
286 
287             /** {@inheritDoc} */
288             @Override
289             public DerivativeStructure[] value(final DerivativeStructure t)
290                 throws MathIllegalArgumentException {
291 
292                 // check we can achieve the requested derivation order with the sample
293                 if (t.getOrder() >= nbPoints) {
294                     throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
295                 }
296 
297                 // compute sample position, trying to be centered if possible
298                 final double t0 = JdkMath.max(JdkMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
299 
300                 // compute sample points
301                 double[][] y = null;
302                 for (int i = 0; i < nbPoints; ++i) {
303                     final double[] v = function.value(t0 + i * stepSize);
304                     if (i == 0) {
305                         y = new double[v.length][nbPoints];
306                     }
307                     for (int j = 0; j < v.length; ++j) {
308                         y[j][i] = v[j];
309                     }
310                 }
311 
312                 // evaluate derivatives
313                 final DerivativeStructure[] value = new DerivativeStructure[y.length];
314                 for (int j = 0; j < value.length; ++j) {
315                     value[j] = evaluate(t, t0, y[j]);
316                 }
317 
318                 return value;
319             }
320         };
321     }
322 
323     /** {@inheritDoc}
324      * <p>The returned object cannot compute derivatives to arbitrary orders. The
325      * value function will throw a {@link NumberIsTooLargeException} if the requested
326      * derivation order is larger or equal to the number of points.
327      * </p>
328      */
329     @Override
330     public UnivariateDifferentiableMatrixFunction differentiate(final UnivariateMatrixFunction function) {
331         return new UnivariateDifferentiableMatrixFunction() {
332 
333             /** {@inheritDoc} */
334             @Override
335             public double[][]  value(final double x) throws MathIllegalArgumentException {
336                 return function.value(x);
337             }
338 
339             /** {@inheritDoc} */
340             @Override
341             public DerivativeStructure[][]  value(final DerivativeStructure t)
342                 throws MathIllegalArgumentException {
343 
344                 // check we can achieve the requested derivation order with the sample
345                 if (t.getOrder() >= nbPoints) {
346                     throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
347                 }
348 
349                 // compute sample position, trying to be centered if possible
350                 final double t0 = JdkMath.max(JdkMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
351 
352                 // compute sample points
353                 double[][][] y = null;
354                 for (int i = 0; i < nbPoints; ++i) {
355                     final double[][] v = function.value(t0 + i * stepSize);
356                     if (i == 0) {
357                         y = new double[v.length][v[0].length][nbPoints];
358                     }
359                     for (int j = 0; j < v.length; ++j) {
360                         for (int k = 0; k < v[j].length; ++k) {
361                             y[j][k][i] = v[j][k];
362                         }
363                     }
364                 }
365 
366                 // evaluate derivatives
367                 final DerivativeStructure[][] value = new DerivativeStructure[y.length][y[0].length];
368                 for (int j = 0; j < value.length; ++j) {
369                     for (int k = 0; k < y[j].length; ++k) {
370                         value[j][k] = evaluate(t, t0, y[j][k]);
371                     }
372                 }
373 
374                 return value;
375             }
376         };
377     }
378 }