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