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  
18  package org.apache.commons.math4.legacy.analysis.differentiation;
19  
20  import org.apache.commons.math4.legacy.analysis.QuinticFunction;
21  import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
22  import org.apache.commons.math4.legacy.analysis.UnivariateMatrixFunction;
23  import org.apache.commons.math4.legacy.analysis.UnivariateVectorFunction;
24  import org.apache.commons.math4.legacy.analysis.function.Gaussian;
25  import org.apache.commons.math4.legacy.analysis.function.Sin;
26  import org.apache.commons.math4.legacy.exception.MathInternalError;
27  import org.apache.commons.math4.legacy.exception.NotPositiveException;
28  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
29  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
30  import org.apache.commons.math4.core.jdkmath.JdkMath;
31  import org.junit.Assert;
32  import org.junit.Test;
33  
34  /**
35   * Test for class {@link FiniteDifferencesDifferentiator}.
36   */
37  public class FiniteDifferencesDifferentiatorTest {
38  
39      @Test(expected=NumberIsTooSmallException.class)
40      public void testWrongNumberOfPoints() {
41          new FiniteDifferencesDifferentiator(1, 1.0);
42      }
43  
44      @Test(expected=NotPositiveException.class)
45      public void testWrongStepSize() {
46          new FiniteDifferencesDifferentiator(3, 0.0);
47      }
48  
49      @Test
50      public void testConstant() {
51          FiniteDifferencesDifferentiator differentiator =
52                  new FiniteDifferencesDifferentiator(5, 0.01);
53          UnivariateDifferentiableFunction f =
54                  differentiator.differentiate(new UnivariateFunction() {
55                      @Override
56                      public double value(double x) {
57                          return 42.0;
58                      }
59                  });
60          for (double x = -10; x < 10; x += 0.1) {
61              DerivativeStructure y = f.value(new DerivativeStructure(1, 2, 0, x));
62              Assert.assertEquals(42.0, y.getValue(), 1.0e-15);
63              Assert.assertEquals( 0.0, y.getPartialDerivative(1), 1.0e-15);
64              Assert.assertEquals( 0.0, y.getPartialDerivative(2), 1.0e-15);
65          }
66      }
67  
68      @Test
69      public void testLinear() {
70          FiniteDifferencesDifferentiator differentiator =
71                  new FiniteDifferencesDifferentiator(5, 0.01);
72          UnivariateDifferentiableFunction f =
73                  differentiator.differentiate(new UnivariateFunction() {
74                      @Override
75                      public double value(double x) {
76                          return 2 - 3 * x;
77                      }
78                  });
79          for (double x = -10; x < 10; x += 0.1) {
80              DerivativeStructure y = f.value(new DerivativeStructure(1, 2, 0, x));
81              Assert.assertEquals("" + (2 - 3 * x - y.getValue()), 2 - 3 * x, y.getValue(), 2.0e-15);
82              Assert.assertEquals(-3.0, y.getPartialDerivative(1), 4.0e-13);
83              Assert.assertEquals( 0.0, y.getPartialDerivative(2), 9.0e-11);
84          }
85      }
86  
87      @Test
88      public void testGaussian() {
89          FiniteDifferencesDifferentiator differentiator =
90                  new FiniteDifferencesDifferentiator(9, 0.02);
91          UnivariateDifferentiableFunction gaussian = new Gaussian(1.0, 2.0);
92          UnivariateDifferentiableFunction f =
93                  differentiator.differentiate(gaussian);
94          double[] expectedError = new double[] {
95              6.939e-18, 1.284e-15, 2.477e-13, 1.168e-11, 2.840e-9, 7.971e-8
96          };
97         double[] maxError = new double[expectedError.length];
98          for (double x = -10; x < 10; x += 0.1) {
99              DerivativeStructure dsX  = new DerivativeStructure(1, maxError.length - 1, 0, x);
100             DerivativeStructure yRef = gaussian.value(dsX);
101             DerivativeStructure y    = f.value(dsX);
102             Assert.assertEquals(f.value(dsX.getValue()), f.value(dsX).getValue(), 1.0e-15);
103             for (int order = 0; order <= yRef.getOrder(); ++order) {
104                 maxError[order] = JdkMath.max(maxError[order],
105                                         JdkMath.abs(yRef.getPartialDerivative(order) -
106                                                      y.getPartialDerivative(order)));
107             }
108         }
109         for (int i = 0; i < maxError.length; ++i) {
110             Assert.assertEquals(expectedError[i], maxError[i], 0.01 * expectedError[i]);
111         }
112     }
113 
114     @Test
115     public void testStepSizeUnstability() {
116         UnivariateDifferentiableFunction quintic = new QuinticFunction();
117         UnivariateDifferentiableFunction goodStep =
118                 new FiniteDifferencesDifferentiator(7, 0.25).differentiate(quintic);
119         UnivariateDifferentiableFunction badStep =
120                 new FiniteDifferencesDifferentiator(7, 1.0e-6).differentiate(quintic);
121         double[] maxErrorGood = new double[7];
122         double[] maxErrorBad  = new double[7];
123         for (double x = -10; x < 10; x += 0.1) {
124             DerivativeStructure dsX  = new DerivativeStructure(1, 6, 0, x);
125             DerivativeStructure yRef  = quintic.value(dsX);
126             DerivativeStructure yGood = goodStep.value(dsX);
127             DerivativeStructure yBad  = badStep.value(dsX);
128             for (int order = 0; order <= 6; ++order) {
129                 maxErrorGood[order] = JdkMath.max(maxErrorGood[order],
130                                                    JdkMath.abs(yRef.getPartialDerivative(order) -
131                                                                 yGood.getPartialDerivative(order)));
132                 maxErrorBad[order]  = JdkMath.max(maxErrorBad[order],
133                                                    JdkMath.abs(yRef.getPartialDerivative(order) -
134                                                                 yBad.getPartialDerivative(order)));
135             }
136         }
137 
138         // the 0.25 step size is good for finite differences in the quintic on this abscissa range for 7 points
139         // the errors are fair
140         final double[] expectedGood = new double[] {
141             7.276e-12, 7.276e-11, 9.968e-10, 3.092e-9, 5.432e-8, 8.196e-8, 1.818e-6
142         };
143 
144         // the 1.0e-6 step size is far too small for finite differences in the quintic on this abscissa range for 7 points
145         // the errors are huge!
146         final double[] expectedBad = new double[] {
147             2.910e-11, 2.087e-5, 147.7, 3.820e7, 6.354e14, 6.548e19, 1.543e27
148         };
149 
150         for (int i = 0; i < maxErrorGood.length; ++i) {
151             Assert.assertEquals(expectedGood[i], maxErrorGood[i], 0.01 * expectedGood[i]);
152             Assert.assertEquals(expectedBad[i],  maxErrorBad[i],  0.01 * expectedBad[i]);
153         }
154     }
155 
156     @Test(expected=NumberIsTooLargeException.class)
157     public void testWrongOrder() {
158         UnivariateDifferentiableFunction f =
159                 new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateFunction() {
160                     @Override
161                     public double value(double x) {
162                         // this exception should not be thrown because wrong order
163                         // should be detected before function call
164                         throw new MathInternalError();
165                     }
166                 });
167         f.value(new DerivativeStructure(1, 3, 0, 1.0));
168     }
169 
170     @Test(expected=NumberIsTooLargeException.class)
171     public void testWrongOrderVector() {
172         UnivariateDifferentiableVectorFunction f =
173                 new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateVectorFunction() {
174                     @Override
175                     public double[] value(double x) {
176                         // this exception should not be thrown because wrong order
177                         // should be detected before function call
178                         throw new MathInternalError();
179                     }
180                 });
181         f.value(new DerivativeStructure(1, 3, 0, 1.0));
182     }
183 
184     @Test(expected=NumberIsTooLargeException.class)
185     public void testWrongOrderMatrix() {
186         UnivariateDifferentiableMatrixFunction f =
187                 new FiniteDifferencesDifferentiator(3, 0.01).differentiate(new UnivariateMatrixFunction() {
188                     @Override
189                     public double[][] value(double x) {
190                         // this exception should not be thrown because wrong order
191                         // should be detected before function call
192                         throw new MathInternalError();
193                     }
194                 });
195         f.value(new DerivativeStructure(1, 3, 0, 1.0));
196     }
197 
198     @Test(expected=NumberIsTooLargeException.class)
199     public void testTooLargeStep() {
200         new FiniteDifferencesDifferentiator(3, 2.5, 0.0, 1.0);
201     }
202 
203     @Test
204     public void testBounds() {
205 
206         final double slope = 2.5;
207         UnivariateFunction f = new UnivariateFunction() {
208             @Override
209             public double value(double x) {
210                 if (x < 0) {
211                     throw new NumberIsTooSmallException(x, 0, true);
212                 } else if (x > 1) {
213                     throw new NumberIsTooLargeException(x, 1, true);
214                 } else {
215                     return slope * x;
216                 }
217             }
218         };
219 
220         UnivariateDifferentiableFunction missingBounds =
221                 new FiniteDifferencesDifferentiator(3, 0.1).differentiate(f);
222         UnivariateDifferentiableFunction properlyBounded =
223                 new FiniteDifferencesDifferentiator(3, 0.1, 0.0, 1.0).differentiate(f);
224         DerivativeStructure tLow  = new DerivativeStructure(1, 1, 0, 0.05);
225         DerivativeStructure tHigh = new DerivativeStructure(1, 1, 0, 0.95);
226 
227         try {
228             // here, we did not set the bounds, so the differences are evaluated out of domain
229             // using f(-0.05), f(0.05), f(0.15)
230             missingBounds.value(tLow);
231             Assert.fail("an exception should have been thrown");
232         } catch (NumberIsTooSmallException nse) {
233             Assert.assertEquals(-0.05, nse.getArgument().doubleValue(), 1.0e-10);
234         } catch (Exception e) {
235             Assert.fail("wrong exception caught: " + e.getClass().getName());
236         }
237 
238         try {
239             // here, we did not set the bounds, so the differences are evaluated out of domain
240             // using f(0.85), f(0.95), f(1.05)
241             missingBounds.value(tHigh);
242             Assert.fail("an exception should have been thrown");
243         } catch (NumberIsTooLargeException nle) {
244             Assert.assertEquals(1.05, nle.getArgument().doubleValue(), 1.0e-10);
245         } catch (Exception e) {
246             Assert.fail("wrong exception caught: " + e.getClass().getName());
247         }
248 
249         // here, we did set the bounds, so evaluations are done within domain
250         // using f(0.0), f(0.1), f(0.2)
251         Assert.assertEquals(slope, properlyBounded.value(tLow).getPartialDerivative(1), 1.0e-10);
252 
253         // here, we did set the bounds, so evaluations are done within domain
254         // using f(0.8), f(0.9), f(1.0)
255         Assert.assertEquals(slope, properlyBounded.value(tHigh).getPartialDerivative(1), 1.0e-10);
256     }
257 
258     @Test
259     public void testBoundedSqrt() {
260 
261         UnivariateFunctionDifferentiator differentiator =
262                 new FiniteDifferencesDifferentiator(9, 1.0 / 32, 0.0, Double.POSITIVE_INFINITY);
263         UnivariateDifferentiableFunction sqrt = differentiator.differentiate(new UnivariateFunction() {
264             @Override
265             public double value(double x) {
266                 return JdkMath.sqrt(x);
267             }
268         });
269 
270         // we are able to compute derivative near 0, but the accuracy is much poorer there
271         DerivativeStructure t001 = new DerivativeStructure(1, 1, 0, 0.01);
272         Assert.assertEquals(0.5 / JdkMath.sqrt(t001.getValue()), sqrt.value(t001).getPartialDerivative(1), 1.6);
273         DerivativeStructure t01 = new DerivativeStructure(1, 1, 0, 0.1);
274         Assert.assertEquals(0.5 / JdkMath.sqrt(t01.getValue()), sqrt.value(t01).getPartialDerivative(1), 7.0e-3);
275         DerivativeStructure t03 = new DerivativeStructure(1, 1, 0, 0.3);
276         Assert.assertEquals(0.5 / JdkMath.sqrt(t03.getValue()), sqrt.value(t03).getPartialDerivative(1), 2.1e-7);
277     }
278 
279     @Test
280     public void testVectorFunction() {
281 
282         FiniteDifferencesDifferentiator differentiator =
283                 new FiniteDifferencesDifferentiator(7, 0.01);
284         UnivariateDifferentiableVectorFunction f =
285                 differentiator.differentiate(new UnivariateVectorFunction() {
286 
287             @Override
288             public double[] value(double x) {
289                 return new double[] { JdkMath.cos(x), JdkMath.sin(x) };
290             }
291         });
292 
293         for (double x = -10; x < 10; x += 0.1) {
294             DerivativeStructure dsX = new DerivativeStructure(1, 2, 0, x);
295             DerivativeStructure[] y = f.value(dsX);
296             double cos = JdkMath.cos(x);
297             double sin = JdkMath.sin(x);
298             double[] f1 = f.value(dsX.getValue());
299             DerivativeStructure[] f2 = f.value(dsX);
300             Assert.assertEquals(f1.length, f2.length);
301             for (int i = 0; i < f1.length; ++i) {
302                 Assert.assertEquals(f1[i], f2[i].getValue(), 1.0e-15);
303             }
304             Assert.assertEquals( cos, y[0].getValue(), 7.0e-16);
305             Assert.assertEquals( sin, y[1].getValue(), 7.0e-16);
306             Assert.assertEquals(-sin, y[0].getPartialDerivative(1), 6.0e-14);
307             Assert.assertEquals( cos, y[1].getPartialDerivative(1), 6.0e-14);
308             Assert.assertEquals(-cos, y[0].getPartialDerivative(2), 2.0e-11);
309             Assert.assertEquals(-sin, y[1].getPartialDerivative(2), 2.0e-11);
310         }
311     }
312 
313     @Test
314     public void testMatrixFunction() {
315 
316         FiniteDifferencesDifferentiator differentiator =
317                 new FiniteDifferencesDifferentiator(7, 0.01);
318         UnivariateDifferentiableMatrixFunction f =
319                 differentiator.differentiate(new UnivariateMatrixFunction() {
320 
321             @Override
322             public double[][] value(double x) {
323                 return new double[][] {
324                     { JdkMath.cos(x),  JdkMath.sin(x)  },
325                     { JdkMath.cosh(x), JdkMath.sinh(x) }
326                 };
327             }
328         });
329 
330         for (double x = -1; x < 1; x += 0.02) {
331             DerivativeStructure dsX = new DerivativeStructure(1, 2, 0, x);
332             DerivativeStructure[][] y = f.value(dsX);
333             double cos = JdkMath.cos(x);
334             double sin = JdkMath.sin(x);
335             double cosh = JdkMath.cosh(x);
336             double sinh = JdkMath.sinh(x);
337             double[][] f1 = f.value(dsX.getValue());
338             DerivativeStructure[][] f2 = f.value(dsX);
339             Assert.assertEquals(f1.length, f2.length);
340             for (int i = 0; i < f1.length; ++i) {
341                 Assert.assertEquals(f1[i].length, f2[i].length);
342                 for (int j = 0; j < f1[i].length; ++j) {
343                     Assert.assertEquals(f1[i][j], f2[i][j].getValue(), 1.0e-15);
344                 }
345             }
346             Assert.assertEquals(cos,   y[0][0].getValue(), 7.0e-18);
347             Assert.assertEquals(sin,   y[0][1].getValue(), 6.0e-17);
348             Assert.assertEquals(cosh,  y[1][0].getValue(), 3.0e-16);
349             Assert.assertEquals(sinh,  y[1][1].getValue(), 3.0e-16);
350             Assert.assertEquals(-sin,  y[0][0].getPartialDerivative(1), 2.0e-14);
351             Assert.assertEquals( cos,  y[0][1].getPartialDerivative(1), 2.0e-14);
352             Assert.assertEquals( sinh, y[1][0].getPartialDerivative(1), 3.0e-14);
353             Assert.assertEquals( cosh, y[1][1].getPartialDerivative(1), 3.0e-14);
354             Assert.assertEquals(-cos,  y[0][0].getPartialDerivative(2), 3.0e-12);
355             Assert.assertEquals(-sin,  y[0][1].getPartialDerivative(2), 3.0e-12);
356             Assert.assertEquals( cosh, y[1][0].getPartialDerivative(2), 6.0e-12);
357             Assert.assertEquals( sinh, y[1][1].getPartialDerivative(2), 6.0e-12);
358         }
359     }
360 
361     @Test
362     public void testSeveralFreeParameters() {
363         FiniteDifferencesDifferentiator differentiator =
364                 new FiniteDifferencesDifferentiator(5, 0.001);
365         UnivariateDifferentiableFunction sine = new Sin();
366         UnivariateDifferentiableFunction f =
367                 differentiator.differentiate(sine);
368         double[] expectedError = new double[] {
369             6.696e-16, 1.371e-12, 2.007e-8, 1.754e-5
370         };
371         double[] maxError = new double[expectedError.length];
372        for (double x = -2; x < 2; x += 0.1) {
373            for (double y = -2; y < 2; y += 0.1) {
374                DerivativeStructure dsX  = new DerivativeStructure(2, maxError.length - 1, 0, x);
375                DerivativeStructure dsY  = new DerivativeStructure(2, maxError.length - 1, 1, y);
376                DerivativeStructure dsT  = dsX.multiply(3).subtract(dsY.multiply(2));
377                DerivativeStructure sRef = sine.value(dsT);
378                DerivativeStructure s    = f.value(dsT);
379                for (int xOrder = 0; xOrder <= sRef.getOrder(); ++xOrder) {
380                    for (int yOrder = 0; yOrder <= sRef.getOrder(); ++yOrder) {
381                        if (xOrder + yOrder <= sRef.getOrder()) {
382                            maxError[xOrder +yOrder] = JdkMath.max(maxError[xOrder + yOrder],
383                                                                     JdkMath.abs(sRef.getPartialDerivative(xOrder, yOrder) -
384                                                                                  s.getPartialDerivative(xOrder, yOrder)));
385                        }
386                    }
387                }
388            }
389        }
390        for (int i = 0; i < maxError.length; ++i) {
391            Assert.assertEquals(expectedError[i], maxError[i], 0.01 * expectedError[i]);
392        }
393     }
394 }