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.interpolation;
18  
19  import java.util.function.DoubleBinaryOperator;
20  import org.apache.commons.math4.legacy.analysis.BivariateFunction;
21  import org.apache.commons.math4.legacy.analysis.interpolation.BicubicInterpolatingFunction.BicubicFunction;
22  import org.apache.commons.statistics.distribution.ContinuousDistribution;
23  import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
24  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
25  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
26  import org.apache.commons.math4.legacy.exception.OutOfRangeException;
27  import org.apache.commons.rng.UniformRandomProvider;
28  import org.apache.commons.rng.simple.RandomSource;
29  import org.apache.commons.math4.core.jdkmath.JdkMath;
30  import org.apache.commons.numbers.core.Precision;
31  import org.junit.Assert;
32  import org.junit.Test;
33  
34  /**
35   * Test case for the bicubic function.
36   */
37  public final class BicubicInterpolatingFunctionTest {
38      /**
39       * Test preconditions.
40       */
41      @Test
42      public void testPreconditions() {
43          double[] xval = new double[] {3, 4, 5, 6.5};
44          double[] yval = new double[] {-4, -3, -1, 2.5};
45          double[][] zval = new double[xval.length][yval.length];
46  
47          @SuppressWarnings("unused")
48          BivariateFunction bcf = new BicubicInterpolatingFunction(xval, yval, zval,
49                                                                   zval, zval, zval);
50  
51          double[] wxval = new double[] {3, 2, 5, 6.5};
52          try {
53              bcf = new BicubicInterpolatingFunction(wxval, yval, zval, zval, zval, zval);
54              Assert.fail("an exception should have been thrown");
55          } catch (MathIllegalArgumentException e) {
56              // Expected
57          }
58          double[] wyval = new double[] {-4, -1, -1, 2.5};
59          try {
60              bcf = new BicubicInterpolatingFunction(xval, wyval, zval, zval, zval, zval);
61              Assert.fail("an exception should have been thrown");
62          } catch (MathIllegalArgumentException e) {
63              // Expected
64          }
65          double[][] wzval = new double[xval.length][yval.length - 1];
66          try {
67              bcf = new BicubicInterpolatingFunction(xval, yval, wzval, zval, zval, zval);
68              Assert.fail("an exception should have been thrown");
69          } catch (DimensionMismatchException e) {
70              // Expected
71          }
72          try {
73              bcf = new BicubicInterpolatingFunction(xval, yval, zval, wzval, zval, zval);
74              Assert.fail("an exception should have been thrown");
75          } catch (DimensionMismatchException e) {
76              // Expected
77          }
78          try {
79              bcf = new BicubicInterpolatingFunction(xval, yval, zval, zval, wzval, zval);
80              Assert.fail("an exception should have been thrown");
81          } catch (DimensionMismatchException e) {
82              // Expected
83          }
84          try {
85              bcf = new BicubicInterpolatingFunction(xval, yval, zval, zval, zval, wzval);
86              Assert.fail("an exception should have been thrown");
87          } catch (DimensionMismatchException e) {
88              // Expected
89          }
90  
91          wzval = new double[xval.length - 1][yval.length];
92          try {
93              bcf = new BicubicInterpolatingFunction(xval, yval, wzval, zval, zval, zval);
94              Assert.fail("an exception should have been thrown");
95          } catch (DimensionMismatchException e) {
96              // Expected
97          }
98          try {
99              bcf = new BicubicInterpolatingFunction(xval, yval, zval, wzval, zval, zval);
100             Assert.fail("an exception should have been thrown");
101         } catch (DimensionMismatchException e) {
102             // Expected
103         }
104         try {
105             bcf = new BicubicInterpolatingFunction(xval, yval, zval, zval, wzval, zval);
106             Assert.fail("an exception should have been thrown");
107         } catch (DimensionMismatchException e) {
108             // Expected
109         }
110         try {
111             bcf = new BicubicInterpolatingFunction(xval, yval, zval, zval, zval, wzval);
112             Assert.fail("an exception should have been thrown");
113         } catch (DimensionMismatchException e) {
114             // Expected
115         }
116     }
117 
118     @Test
119     public void testIsValidPoint() {
120         final double xMin = -12;
121         final double xMax = 34;
122         final double yMin = 5;
123         final double yMax = 67;
124         final double[] xval = new double[] { xMin, xMax };
125         final double[] yval = new double[] { yMin, yMax };
126         final double[][] f = new double[][] { { 1, 2 },
127                                               { 3, 4 } };
128         final double[][] dFdX = f;
129         final double[][] dFdY = f;
130         final double[][] dFdXdY = f;
131 
132         final BicubicInterpolatingFunction bcf
133             = new BicubicInterpolatingFunction(xval, yval, f,
134                                                      dFdX, dFdY, dFdXdY);
135 
136         double x = xMin;
137         double y = yMin;
138         Assert.assertTrue(bcf.isValidPoint(x, y));
139         // Ensure that no exception is thrown.
140         bcf.value(x, y);
141 
142         x = xMax;
143         y = yMax;
144         Assert.assertTrue(bcf.isValidPoint(x, y));
145         // Ensure that no exception is thrown.
146         bcf.value(x, y);
147 
148         final double xRange = xMax - xMin;
149         final double yRange = yMax - yMin;
150         x = xMin + xRange / 3.4;
151         y = yMin + yRange / 1.2;
152         Assert.assertTrue(bcf.isValidPoint(x, y));
153         // Ensure that no exception is thrown.
154         bcf.value(x, y);
155 
156         final double small = 1e-8;
157         x = xMin - small;
158         y = yMax;
159         Assert.assertFalse(bcf.isValidPoint(x, y));
160         // Ensure that an exception would have been thrown.
161         try {
162             bcf.value(x, y);
163             Assert.fail("OutOfRangeException expected");
164         } catch (OutOfRangeException expected) {}
165 
166         x = xMin;
167         y = yMax + small;
168         Assert.assertFalse(bcf.isValidPoint(x, y));
169         // Ensure that an exception would have been thrown.
170         try {
171             bcf.value(x, y);
172             Assert.fail("OutOfRangeException expected");
173         } catch (OutOfRangeException expected) {}
174     }
175 
176     /**
177      * Interpolating a plane.
178      * \[
179      *   z = 2 x - 3 y + 5
180      * \]
181      */
182     @Test
183     public void testPlane() {
184         final int numberOfElements = 10;
185         final double minimumX = -10;
186         final double maximumX = 10;
187         final double minimumY = -10;
188         final double maximumY = 10;
189         final int numberOfSamples = 1000;
190 
191         final double interpolationTolerance = 1e-15;
192         final double maxTolerance = 1e-14;
193 
194         // Function values
195         BivariateFunction f = new BivariateFunction() {
196                 @Override
197                 public double value(double x, double y) {
198                     return 2 * x - 3 * y + 5;
199                 }
200             };
201         BivariateFunction dfdx = new BivariateFunction() {
202                 @Override
203                 public double value(double x, double y) {
204                     return 2;
205                 }
206             };
207         BivariateFunction dfdy = new BivariateFunction() {
208                 @Override
209                 public double value(double x, double y) {
210                     return -3;
211                 }
212             };
213         BivariateFunction d2fdxdy = new BivariateFunction() {
214                 @Override
215                 public double value(double x, double y) {
216                     return 0;
217                 }
218             };
219 
220         testInterpolation(minimumX,
221                           maximumX,
222                           minimumY,
223                           maximumY,
224                           numberOfElements,
225                           numberOfSamples,
226                           f,
227                           dfdx,
228                           dfdy,
229                           d2fdxdy,
230                           interpolationTolerance,
231                           maxTolerance,
232                           false);
233     }
234 
235     /**
236      * Interpolating a paraboloid.
237      * \[
238      *   z = 2 x^2 - 3 y^2 + 4 x y - 5
239      * \]
240      */
241     @Test
242     public void testParaboloid() {
243         final int numberOfElements = 10;
244         final double minimumX = -10;
245         final double maximumX = 10;
246         final double minimumY = -10;
247         final double maximumY = 10;
248         final int numberOfSamples = 1000;
249 
250         final double interpolationTolerance = 2e-14;
251         final double maxTolerance = 1e-12;
252 
253         // Function values
254         BivariateFunction f = new BivariateFunction() {
255                 @Override
256                 public double value(double x, double y) {
257                     return 2 * x * x - 3 * y * y + 4 * x * y - 5;
258                 }
259             };
260         BivariateFunction dfdx = new BivariateFunction() {
261                 @Override
262                 public double value(double x, double y) {
263                     return 4 * (x + y);
264                 }
265             };
266         BivariateFunction dfdy = new BivariateFunction() {
267                 @Override
268                 public double value(double x, double y) {
269                     return 4 * x - 6 * y;
270                 }
271             };
272         BivariateFunction d2fdxdy = new BivariateFunction() {
273                 @Override
274                 public double value(double x, double y) {
275                     return 4;
276                 }
277             };
278 
279         testInterpolation(minimumX,
280                           maximumX,
281                           minimumY,
282                           maximumY,
283                           numberOfElements,
284                           numberOfSamples,
285                           f,
286                           dfdx,
287                           dfdy,
288                           d2fdxdy,
289                           interpolationTolerance,
290                           maxTolerance,
291                           false);
292     }
293 
294     /**
295      * Test for partial derivatives of {@link BicubicFunction}.
296      * \[
297      *   f(x, y) = \Sigma_i \Sigma_j (i+1) (j+2) x^i y^j
298      * \]
299      */
300     @Test
301     public void testSplinePartialDerivatives() {
302         final int N = 4;
303         final double[] coeff = new double[16];
304 
305         for (int i = 0; i < N; i++) {
306             for (int j = 0; j < N; j++) {
307                 coeff[i + N * j] = (i + 1) * (j + 2);
308             }
309         }
310 
311         final BicubicFunction f = new BicubicFunction(coeff, 1, 1, true);
312         BivariateFunction derivative;
313         final double x = 0.435;
314         final double y = 0.776;
315         final double tol = 1e-13;
316 
317         derivative = new BivariateFunction() {
318                 public double value(double x, double y) {
319                     final double x2 = x * x;
320                     final double y2 = y * y;
321                     final double y3 = y2 * y;
322                     final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
323                     return yFactor * (2 + 6 * x + 12 * x2);
324                 }
325             };
326         Assert.assertEquals("dFdX", derivative.value(x, y),
327                             f.partialDerivativeX().value(x, y), tol);
328 
329         derivative = new BivariateFunction() {
330                 public double value(double x, double y) {
331                     final double x2 = x * x;
332                     final double x3 = x2 * x;
333                     final double y2 = y * y;
334                     final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
335                     return xFactor * (3 + 8 * y + 15 * y2);
336                 }
337             };
338         Assert.assertEquals("dFdY", derivative.value(x, y),
339                             f.partialDerivativeY().value(x, y), tol);
340 
341         derivative = new BivariateFunction() {
342                 public double value(double x, double y) {
343                     final double y2 = y * y;
344                     final double y3 = y2 * y;
345                     final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
346                     return yFactor * (6 + 24 * x);
347                 }
348             };
349         Assert.assertEquals("d2FdX2", derivative.value(x, y),
350                             f.partialDerivativeXX().value(x, y), tol);
351 
352         derivative = new BivariateFunction() {
353                 public double value(double x, double y) {
354                     final double x2 = x * x;
355                     final double x3 = x2 * x;
356                     final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
357                     return xFactor * (8 + 30 * y);
358                 }
359             };
360         Assert.assertEquals("d2FdY2", derivative.value(x, y),
361                             f.partialDerivativeYY().value(x, y), tol);
362 
363         derivative = new BivariateFunction() {
364                 public double value(double x, double y) {
365                     final double x2 = x * x;
366                     final double y2 = y * y;
367                     final double yFactor = 3 + 8 * y + 15 * y2;
368                     return yFactor * (2 + 6 * x + 12 * x2);
369                 }
370             };
371         Assert.assertEquals("d2FdXdY", derivative.value(x, y),
372                             f.partialDerivativeXY().value(x, y), tol);
373     }
374 
375     /**
376      * Test that the partial derivatives computed from a
377      * {@link BicubicInterpolatingFunction} match the input data.
378      * \[
379      *   f(x, y) = 5
380      *             - 3 x + 2 y
381      *             - x y + 2 x^2 - 3 y^2
382      *             + 4 x^2 y - x y^2 - 3 x^3 + y^3
383      * \]
384      */
385     @Test
386     public void testMatchingPartialDerivatives() {
387         final int sz = 21;
388         double[] xval = new double[sz];
389         double[] yval = new double[sz];
390         // Coordinate values
391         final double delta = 1d / (sz - 1);
392         for (int i = 0; i < sz; i++) {
393             xval[i] = i * delta;
394             yval[i] = i * delta / 3;
395         }
396         // Function values
397         BivariateFunction f = new BivariateFunction() {
398                 public double value(double x, double y) {
399                     final double x2 = x * x;
400                     final double x3 = x2 * x;
401                     final double y2 = y * y;
402                     final double y3 = y2 * y;
403 
404                     return 5
405                         - 3 * x + 2 * y
406                         - x * y + 2 * x2 - 3 * y2
407                         + 4 * x2 * y - x * y2 - 3 * x3 + y3;
408                 }
409             };
410         double[][] fval = new double[sz][sz];
411         for (int i = 0; i < sz; i++) {
412             for (int j = 0; j < sz; j++) {
413                 fval[i][j] = f.value(xval[i], yval[j]);
414             }
415         }
416         // Partial derivatives with respect to x
417         double[][] dFdX = new double[sz][sz];
418         BivariateFunction dfdX = new BivariateFunction() {
419                 public double value(double x, double y) {
420                     final double x2 = x * x;
421                     final double y2 = y * y;
422                     return - 3 - y + 4 * x + 8 * x * y - y2 - 9 * x2;
423                 }
424             };
425         for (int i = 0; i < sz; i++) {
426             for (int j = 0; j < sz; j++) {
427                 dFdX[i][j] = dfdX.value(xval[i], yval[j]);
428             }
429         }
430         // Partial derivatives with respect to y
431         double[][] dFdY = new double[sz][sz];
432         BivariateFunction dfdY = new BivariateFunction() {
433                 public double value(double x, double y) {
434                     final double x2 = x * x;
435                     final double y2 = y * y;
436                     return 2 - x - 6 * y + 4 * x2 - 2 * x * y + 3 * y2;
437                 }
438             };
439         for (int i = 0; i < sz; i++) {
440             for (int j = 0; j < sz; j++) {
441                 dFdY[i][j] = dfdY.value(xval[i], yval[j]);
442             }
443         }
444         // Second partial derivatives with respect to x
445         double[][] d2Fd2X = new double[sz][sz];
446         BivariateFunction d2fd2X = new BivariateFunction() {
447                 public double value(double x, double y) {
448                     return 4 + 8 * y - 18 * x;
449                 }
450             };
451         for (int i = 0; i < sz; i++) {
452             for (int j = 0; j < sz; j++) {
453                 d2Fd2X[i][j] = d2fd2X.value(xval[i], yval[j]);
454             }
455         }
456         // Second partial derivatives with respect to y
457         double[][] d2Fd2Y = new double[sz][sz];
458         BivariateFunction d2fd2Y = new BivariateFunction() {
459                 public double value(double x, double y) {
460                     return - 6 - 2 * x + 6 * y;
461                 }
462             };
463         for (int i = 0; i < sz; i++) {
464             for (int j = 0; j < sz; j++) {
465                 d2Fd2Y[i][j] = d2fd2Y.value(xval[i], yval[j]);
466             }
467         }
468         // Partial cross-derivatives
469         double[][] d2FdXdY = new double[sz][sz];
470         BivariateFunction d2fdXdY = new BivariateFunction() {
471                 public double value(double x, double y) {
472                     return -1 + 8 * x - 2 * y;
473                 }
474             };
475         for (int i = 0; i < sz; i++) {
476             for (int j = 0; j < sz; j++) {
477                 d2FdXdY[i][j] = d2fdXdY.value(xval[i], yval[j]);
478             }
479         }
480 
481         BicubicInterpolatingFunction bcf
482             = new BicubicInterpolatingFunction(xval, yval, fval, dFdX, dFdY, d2FdXdY, true);
483         DoubleBinaryOperator partialDerivativeX = bcf.partialDerivativeX();
484         DoubleBinaryOperator partialDerivativeY = bcf.partialDerivativeY();
485         DoubleBinaryOperator partialDerivativeXX = bcf.partialDerivativeXX();
486         DoubleBinaryOperator partialDerivativeYY = bcf.partialDerivativeYY();
487         DoubleBinaryOperator partialDerivativeXY = bcf.partialDerivativeXY();
488 
489         double x;
490         double y;
491         double expected;
492         double result;
493 
494         final double tol = 1e-10;
495         for (int i = 0; i < sz; i++) {
496             x = xval[i];
497             for (int j = 0; j < sz; j++) {
498                 y = yval[j];
499 
500                 expected = dfdX.value(x, y);
501                 result = partialDerivativeX.applyAsDouble(x, y);
502                 Assert.assertEquals(x + " " + y + " dFdX", expected, result, tol);
503 
504                 expected = dfdY.value(x, y);
505                 result = partialDerivativeY.applyAsDouble(x, y);
506                 Assert.assertEquals(x + " " + y + " dFdY", expected, result, tol);
507 
508                 expected = d2fd2X.value(x, y);
509                 result = partialDerivativeXX.applyAsDouble(x, y);
510                 Assert.assertEquals(x + " " + y + " d2Fd2X", expected, result, tol);
511 
512                 expected = d2fd2Y.value(x, y);
513                 result = partialDerivativeYY.applyAsDouble(x, y);
514                 Assert.assertEquals(x + " " + y + " d2Fd2Y", expected, result, tol);
515 
516                 expected = d2fdXdY.value(x, y);
517                 result = partialDerivativeXY.applyAsDouble(x, y);
518                 Assert.assertEquals(x + " " + y + " d2FdXdY", expected, result, tol);
519             }
520         }
521     }
522 
523     /**
524      * @param minimumX Lower bound of interpolation range along the x-coordinate.
525      * @param maximumX Higher bound of interpolation range along the x-coordinate.
526      * @param minimumY Lower bound of interpolation range along the y-coordinate.
527      * @param maximumY Higher bound of interpolation range along the y-coordinate.
528      * @param numberOfElements Number of data points (along each dimension).
529      * @param numberOfSamples Number of test points.
530      * @param f Function to test.
531      * @param dfdx Partial derivative w.r.t. x of the function to test.
532      * @param dfdy Partial derivative w.r.t. y of the function to test.
533      * @param d2fdxdy Second partial cross-derivative of the function to test.
534      * @param meanTolerance Allowed average error (mean error on all interpolated values).
535      * @param maxTolerance Allowed error on each interpolated value.
536      */
537     private void testInterpolation(double minimumX,
538                                    double maximumX,
539                                    double minimumY,
540                                    double maximumY,
541                                    int numberOfElements,
542                                    int numberOfSamples,
543                                    BivariateFunction f,
544                                    BivariateFunction dfdx,
545                                    BivariateFunction dfdy,
546                                    BivariateFunction d2fdxdy,
547                                    double meanTolerance,
548                                    double maxTolerance,
549                                    boolean print) {
550         double expected;
551         double actual;
552         double currentX;
553         double currentY;
554         final double deltaX = (maximumX - minimumX) / numberOfElements;
555         final double deltaY = (maximumY - minimumY) / numberOfElements;
556         final double[] xValues = new double[numberOfElements];
557         final double[] yValues = new double[numberOfElements];
558         final double[][] zValues = new double[numberOfElements][numberOfElements];
559         final double[][] dzdx = new double[numberOfElements][numberOfElements];
560         final double[][] dzdy = new double[numberOfElements][numberOfElements];
561         final double[][] d2zdxdy = new double[numberOfElements][numberOfElements];
562 
563         for (int i = 0; i < numberOfElements; i++) {
564             xValues[i] = minimumX + deltaX * i;
565             final double x = xValues[i];
566             for (int j = 0; j < numberOfElements; j++) {
567                 yValues[j] = minimumY + deltaY * j;
568                 final double y = yValues[j];
569                 zValues[i][j] = f.value(x, y);
570                 dzdx[i][j] = dfdx.value(x, y);
571                 dzdy[i][j] = dfdy.value(x, y);
572                 d2zdxdy[i][j] = d2fdxdy.value(x, y);
573             }
574         }
575 
576         final BivariateFunction interpolation
577             = new BicubicInterpolatingFunction(xValues,
578                                                yValues,
579                                                zValues,
580                                                dzdx,
581                                                dzdy,
582                                                d2zdxdy);
583 
584         for (int i = 0; i < numberOfElements; i++) {
585             currentX = xValues[i];
586             for (int j = 0; j < numberOfElements; j++) {
587                 currentY = yValues[j];
588                 expected = f.value(currentX, currentY);
589                 actual = interpolation.value(currentX, currentY);
590                 Assert.assertTrue("On data point: " + expected + " != " + actual,
591                                   Precision.equals(expected, actual));
592             }
593         }
594 
595         final UniformRandomProvider rng = RandomSource.WELL_19937_C.create(1234567L);
596         final ContinuousDistribution.Sampler distX = UniformContinuousDistribution.of(xValues[0], xValues[xValues.length - 1]).createSampler(rng);
597         final ContinuousDistribution.Sampler distY = UniformContinuousDistribution.of(yValues[0], yValues[yValues.length - 1]).createSampler(rng);
598 
599         double sumError = 0;
600         for (int i = 0; i < numberOfSamples; i++) {
601             currentX = distX.sample();
602             currentY = distY.sample();
603             expected = f.value(currentX, currentY);
604 
605             if (print) {
606                 System.out.println(currentX + " " + currentY + " -> ");
607             }
608 
609             actual = interpolation.value(currentX, currentY);
610             sumError += JdkMath.abs(actual - expected);
611 
612             if (print) {
613                 System.out.println(actual + " (diff=" + (expected - actual) + ")");
614             }
615 
616             Assert.assertEquals(expected, actual, maxTolerance);
617         }
618 
619         final double meanError = sumError / numberOfSamples;
620         Assert.assertEquals(0, meanError, meanTolerance);
621     }
622 }