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.math3.analysis.interpolation;
18  
19  import org.apache.commons.math3.analysis.BivariateFunction;
20  import org.apache.commons.math3.exception.DimensionMismatchException;
21  import org.apache.commons.math3.exception.NoDataException;
22  import org.apache.commons.math3.exception.OutOfRangeException;
23  import org.apache.commons.math3.exception.NonMonotonicSequenceException;
24  import org.apache.commons.math3.util.MathArrays;
25  
26  /**
27   * Function that implements the
28   * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
29   * bicubic spline interpolation</a>.
30   *
31   * @since 2.1
32   * @version $Id: BicubicSplineInterpolatingFunction.java 1379904 2012-09-01 23:54:52Z erans $
33   */
34  public class BicubicSplineInterpolatingFunction
35      implements BivariateFunction {
36      /**
37       * Matrix to compute the spline coefficients from the function values
38       * and function derivatives values
39       */
40      private static final double[][] AINV = {
41          { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
42          { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
43          { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 },
44          { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 },
45          { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
46          { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 },
47          { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 },
48          { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 },
49          { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
50          { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 },
51          { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 },
52          { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 },
53          { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 },
54          { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 },
55          { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 },
56          { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 }
57      };
58  
59      /** Samples x-coordinates */
60      private final double[] xval;
61      /** Samples y-coordinates */
62      private final double[] yval;
63      /** Set of cubic splines patching the whole data grid */
64      private final BicubicSplineFunction[][] splines;
65      /**
66       * Partial derivatives
67       * The value of the first index determines the kind of derivatives:
68       * 0 = first partial derivatives wrt x
69       * 1 = first partial derivatives wrt y
70       * 2 = second partial derivatives wrt x
71       * 3 = second partial derivatives wrt y
72       * 4 = cross partial derivatives
73       */
74      private BivariateFunction[][][] partialDerivatives = null;
75  
76      /**
77       * @param x Sample values of the x-coordinate, in increasing order.
78       * @param y Sample values of the y-coordinate, in increasing order.
79       * @param f Values of the function on every grid point.
80       * @param dFdX Values of the partial derivative of function with respect
81       * to x on every grid point.
82       * @param dFdY Values of the partial derivative of function with respect
83       * to y on every grid point.
84       * @param d2FdXdY Values of the cross partial derivative of function on
85       * every grid point.
86       * @throws DimensionMismatchException if the various arrays do not contain
87       * the expected number of elements.
88       * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
89       * not strictly increasing.
90       * @throws NoDataException if any of the arrays has zero length.
91       */
92      public BicubicSplineInterpolatingFunction(double[] x,
93                                                double[] y,
94                                                double[][] f,
95                                                double[][] dFdX,
96                                                double[][] dFdY,
97                                                double[][] d2FdXdY)
98          throws DimensionMismatchException,
99                 NoDataException,
100                NonMonotonicSequenceException {
101         final int xLen = x.length;
102         final int yLen = y.length;
103 
104         if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
105             throw new NoDataException();
106         }
107         if (xLen != f.length) {
108             throw new DimensionMismatchException(xLen, f.length);
109         }
110         if (xLen != dFdX.length) {
111             throw new DimensionMismatchException(xLen, dFdX.length);
112         }
113         if (xLen != dFdY.length) {
114             throw new DimensionMismatchException(xLen, dFdY.length);
115         }
116         if (xLen != d2FdXdY.length) {
117             throw new DimensionMismatchException(xLen, d2FdXdY.length);
118         }
119 
120         MathArrays.checkOrder(x);
121         MathArrays.checkOrder(y);
122 
123         xval = x.clone();
124         yval = y.clone();
125 
126         final int lastI = xLen - 1;
127         final int lastJ = yLen - 1;
128         splines = new BicubicSplineFunction[lastI][lastJ];
129 
130         for (int i = 0; i < lastI; i++) {
131             if (f[i].length != yLen) {
132                 throw new DimensionMismatchException(f[i].length, yLen);
133             }
134             if (dFdX[i].length != yLen) {
135                 throw new DimensionMismatchException(dFdX[i].length, yLen);
136             }
137             if (dFdY[i].length != yLen) {
138                 throw new DimensionMismatchException(dFdY[i].length, yLen);
139             }
140             if (d2FdXdY[i].length != yLen) {
141                 throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
142             }
143             final int ip1 = i + 1;
144             for (int j = 0; j < lastJ; j++) {
145                 final int jp1 = j + 1;
146                 final double[] beta = new double[] {
147                     f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1],
148                     dFdX[i][j], dFdX[ip1][j], dFdX[i][jp1], dFdX[ip1][jp1],
149                     dFdY[i][j], dFdY[ip1][j], dFdY[i][jp1], dFdY[ip1][jp1],
150                     d2FdXdY[i][j], d2FdXdY[ip1][j], d2FdXdY[i][jp1], d2FdXdY[ip1][jp1]
151                 };
152 
153                 splines[i][j] = new BicubicSplineFunction(computeSplineCoefficients(beta));
154             }
155         }
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     public double value(double x, double y)
162         throws OutOfRangeException {
163         final int i = searchIndex(x, xval);
164         if (i == -1) {
165             throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]);
166         }
167         final int j = searchIndex(y, yval);
168         if (j == -1) {
169             throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]);
170         }
171 
172         final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
173         final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
174 
175         return splines[i][j].value(xN, yN);
176     }
177 
178     /**
179      * @param x x-coordinate.
180      * @param y y-coordinate.
181      * @return the value at point (x, y) of the first partial derivative with
182      * respect to x.
183      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
184      * the range defined by the boundary values of {@code xval} (resp.
185      * {@code yval}).
186      */
187     public double partialDerivativeX(double x, double y)
188         throws OutOfRangeException {
189         return partialDerivative(0, x, y);
190     }
191     /**
192      * @param x x-coordinate.
193      * @param y y-coordinate.
194      * @return the value at point (x, y) of the first partial derivative with
195      * respect to y.
196      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
197      * the range defined by the boundary values of {@code xval} (resp.
198      * {@code yval}).
199      */
200     public double partialDerivativeY(double x, double y)
201         throws OutOfRangeException {
202         return partialDerivative(1, x, y);
203     }
204     /**
205      * @param x x-coordinate.
206      * @param y y-coordinate.
207      * @return the value at point (x, y) of the second partial derivative with
208      * respect to x.
209      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
210      * the range defined by the boundary values of {@code xval} (resp.
211      * {@code yval}).
212      */
213     public double partialDerivativeXX(double x, double y)
214         throws OutOfRangeException {
215         return partialDerivative(2, x, y);
216     }
217     /**
218      * @param x x-coordinate.
219      * @param y y-coordinate.
220      * @return the value at point (x, y) of the second partial derivative with
221      * respect to y.
222      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
223      * the range defined by the boundary values of {@code xval} (resp.
224      * {@code yval}).
225      */
226     public double partialDerivativeYY(double x, double y)
227         throws OutOfRangeException {
228         return partialDerivative(3, x, y);
229     }
230     /**
231      * @param x x-coordinate.
232      * @param y y-coordinate.
233      * @return the value at point (x, y) of the second partial cross-derivative.
234      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
235      * the range defined by the boundary values of {@code xval} (resp.
236      * {@code yval}).
237      */
238     public double partialDerivativeXY(double x, double y)
239         throws OutOfRangeException {
240         return partialDerivative(4, x, y);
241     }
242 
243     /**
244      * @param which First index in {@link #partialDerivatives}.
245      * @param x x-coordinate.
246      * @param y y-coordinate.
247      * @return the value at point (x, y) of the selected partial derivative.
248      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
249      * the range defined by the boundary values of {@code xval} (resp.
250      * {@code yval}).
251      */
252     private double partialDerivative(int which, double x, double y)
253         throws OutOfRangeException {
254         if (partialDerivatives == null) {
255             computePartialDerivatives();
256         }
257 
258         final int i = searchIndex(x, xval);
259         if (i == -1) {
260             throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]);
261         }
262         final int j = searchIndex(y, yval);
263         if (j == -1) {
264             throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]);
265         }
266 
267         final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
268         final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
269 
270         return partialDerivatives[which][i][j].value(xN, yN);
271     }
272 
273     /**
274      * Compute all partial derivatives.
275      */
276     private void computePartialDerivatives() {
277         final int lastI = xval.length - 1;
278         final int lastJ = yval.length - 1;
279         partialDerivatives = new BivariateFunction[5][lastI][lastJ];
280 
281         for (int i = 0; i < lastI; i++) {
282             for (int j = 0; j < lastJ; j++) {
283                 final BicubicSplineFunction f = splines[i][j];
284                 partialDerivatives[0][i][j] = f.partialDerivativeX();
285                 partialDerivatives[1][i][j] = f.partialDerivativeY();
286                 partialDerivatives[2][i][j] = f.partialDerivativeXX();
287                 partialDerivatives[3][i][j] = f.partialDerivativeYY();
288                 partialDerivatives[4][i][j] = f.partialDerivativeXY();
289             }
290         }
291     }
292 
293     /**
294      * @param c Coordinate.
295      * @param val Coordinate samples.
296      * @return the index in {@code val} corresponding to the interval
297      * containing {@code c}, or {@code -1} if {@code c} is out of the
298      * range defined by the boundary values of {@code val}.
299      */
300     private int searchIndex(double c, double[] val) {
301         if (c < val[0]) {
302             return -1;
303         }
304 
305         final int max = val.length;
306         for (int i = 1; i < max; i++) {
307             if (c <= val[i]) {
308                 return i - 1;
309             }
310         }
311 
312         return -1;
313     }
314 
315     /**
316      * Compute the spline coefficients from the list of function values and
317      * function partial derivatives values at the four corners of a grid
318      * element. They must be specified in the following order:
319      * <ul>
320      *  <li>f(0,0)</li>
321      *  <li>f(1,0)</li>
322      *  <li>f(0,1)</li>
323      *  <li>f(1,1)</li>
324      *  <li>f<sub>x</sub>(0,0)</li>
325      *  <li>f<sub>x</sub>(1,0)</li>
326      *  <li>f<sub>x</sub>(0,1)</li>
327      *  <li>f<sub>x</sub>(1,1)</li>
328      *  <li>f<sub>y</sub>(0,0)</li>
329      *  <li>f<sub>y</sub>(1,0)</li>
330      *  <li>f<sub>y</sub>(0,1)</li>
331      *  <li>f<sub>y</sub>(1,1)</li>
332      *  <li>f<sub>xy</sub>(0,0)</li>
333      *  <li>f<sub>xy</sub>(1,0)</li>
334      *  <li>f<sub>xy</sub>(0,1)</li>
335      *  <li>f<sub>xy</sub>(1,1)</li>
336      * </ul>
337      * where the subscripts indicate the partial derivative with respect to
338      * the corresponding variable(s).
339      *
340      * @param beta List of function values and function partial derivatives
341      * values.
342      * @return the spline coefficients.
343      */
344     private double[] computeSplineCoefficients(double[] beta) {
345         final double[] a = new double[16];
346 
347         for (int i = 0; i < 16; i++) {
348             double result = 0;
349             final double[] row = AINV[i];
350             for (int j = 0; j < 16; j++) {
351                 result += row[j] * beta[j];
352             }
353             a[i] = result;
354         }
355 
356         return a;
357     }
358 }
359 
360 /**
361  * 2D-spline function.
362  *
363  * @version $Id: BicubicSplineInterpolatingFunction.java 1379904 2012-09-01 23:54:52Z erans $
364  */
365 class BicubicSplineFunction
366     implements BivariateFunction {
367 
368     /** Number of points. */
369     private static final short N = 4;
370 
371     /** Coefficients */
372     private final double[][] a;
373 
374     /** First partial derivative along x. */
375     private BivariateFunction partialDerivativeX;
376 
377     /** First partial derivative along y. */
378     private BivariateFunction partialDerivativeY;
379 
380     /** Second partial derivative along x. */
381     private BivariateFunction partialDerivativeXX;
382 
383     /** Second partial derivative along y. */
384     private BivariateFunction partialDerivativeYY;
385 
386     /** Second crossed partial derivative. */
387     private BivariateFunction partialDerivativeXY;
388 
389     /**
390      * Simple constructor.
391      * @param a Spline coefficients
392      */
393     public BicubicSplineFunction(double[] a) {
394         this.a = new double[N][N];
395         for (int i = 0; i < N; i++) {
396             for (int j = 0; j < N; j++) {
397                 this.a[i][j] = a[i + N * j];
398             }
399         }
400     }
401 
402     /**
403      * {@inheritDoc}
404      */
405     public double value(double x, double y) {
406         if (x < 0 || x > 1) {
407             throw new OutOfRangeException(x, 0, 1);
408         }
409         if (y < 0 || y > 1) {
410             throw new OutOfRangeException(y, 0, 1);
411         }
412 
413         final double x2 = x * x;
414         final double x3 = x2 * x;
415         final double[] pX = {1, x, x2, x3};
416 
417         final double y2 = y * y;
418         final double y3 = y2 * y;
419         final double[] pY = {1, y, y2, y3};
420 
421         return apply(pX, pY, a);
422     }
423 
424     /**
425      * Compute the value of the bicubic polynomial.
426      *
427      * @param pX Powers of the x-coordinate.
428      * @param pY Powers of the y-coordinate.
429      * @param coeff Spline coefficients.
430      * @return the interpolated value.
431      */
432     private double apply(double[] pX, double[] pY, double[][] coeff) {
433         double result = 0;
434         for (int i = 0; i < N; i++) {
435             for (int j = 0; j < N; j++) {
436                 result += coeff[i][j] * pX[i] * pY[j];
437             }
438         }
439 
440         return result;
441     }
442 
443     /**
444      * @return the partial derivative wrt {@code x}.
445      */
446     public BivariateFunction partialDerivativeX() {
447         if (partialDerivativeX == null) {
448             computePartialDerivatives();
449         }
450 
451         return partialDerivativeX;
452     }
453     /**
454      * @return the partial derivative wrt {@code y}.
455      */
456     public BivariateFunction partialDerivativeY() {
457         if (partialDerivativeY == null) {
458             computePartialDerivatives();
459         }
460 
461         return partialDerivativeY;
462     }
463     /**
464      * @return the second partial derivative wrt {@code x}.
465      */
466     public BivariateFunction partialDerivativeXX() {
467         if (partialDerivativeXX == null) {
468             computePartialDerivatives();
469         }
470 
471         return partialDerivativeXX;
472     }
473     /**
474      * @return the second partial derivative wrt {@code y}.
475      */
476     public BivariateFunction partialDerivativeYY() {
477         if (partialDerivativeYY == null) {
478             computePartialDerivatives();
479         }
480 
481         return partialDerivativeYY;
482     }
483     /**
484      * @return the second partial cross-derivative.
485      */
486     public BivariateFunction partialDerivativeXY() {
487         if (partialDerivativeXY == null) {
488             computePartialDerivatives();
489         }
490 
491         return partialDerivativeXY;
492     }
493 
494     /**
495      * Compute all partial derivatives functions.
496      */
497     private void computePartialDerivatives() {
498         final double[][] aX = new double[N][N];
499         final double[][] aY = new double[N][N];
500         final double[][] aXX = new double[N][N];
501         final double[][] aYY = new double[N][N];
502         final double[][] aXY = new double[N][N];
503 
504         for (int i = 0; i < N; i++) {
505             for (int j = 0; j < N; j++) {
506                 final double c = a[i][j];
507                 aX[i][j] = i * c;
508                 aY[i][j] = j * c;
509                 aXX[i][j] = (i - 1) * aX[i][j];
510                 aYY[i][j] = (j - 1) * aY[i][j];
511                 aXY[i][j] = j * aX[i][j];
512             }
513         }
514 
515         partialDerivativeX = new BivariateFunction() {
516                 public double value(double x, double y)  {
517                     final double x2 = x * x;
518                     final double[] pX = {0, 1, x, x2};
519 
520                     final double y2 = y * y;
521                     final double y3 = y2 * y;
522                     final double[] pY = {1, y, y2, y3};
523 
524                     return apply(pX, pY, aX);
525                 }
526             };
527         partialDerivativeY = new BivariateFunction() {
528                 public double value(double x, double y)  {
529                     final double x2 = x * x;
530                     final double x3 = x2 * x;
531                     final double[] pX = {1, x, x2, x3};
532 
533                     final double y2 = y * y;
534                     final double[] pY = {0, 1, y, y2};
535 
536                     return apply(pX, pY, aY);
537                 }
538             };
539         partialDerivativeXX = new BivariateFunction() {
540                 public double value(double x, double y)  {
541                     final double[] pX = {0, 0, 1, x};
542 
543                     final double y2 = y * y;
544                     final double y3 = y2 * y;
545                     final double[] pY = {1, y, y2, y3};
546 
547                     return apply(pX, pY, aXX);
548                 }
549             };
550         partialDerivativeYY = new BivariateFunction() {
551                 public double value(double x, double y)  {
552                     final double x2 = x * x;
553                     final double x3 = x2 * x;
554                     final double[] pX = {1, x, x2, x3};
555 
556                     final double[] pY = {0, 0, 1, y};
557 
558                     return apply(pX, pY, aYY);
559                 }
560             };
561         partialDerivativeXY = new BivariateFunction() {
562                 public double value(double x, double y)  {
563                     final double x2 = x * x;
564                     final double[] pX = {0, 1, x, x2};
565 
566                     final double y2 = y * y;
567                     final double[] pY = {0, 1, y, y2};
568 
569                     return apply(pX, pY, aXY);
570                 }
571             };
572     }
573 }