BicubicSplineInterpolatingFunction.java

  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. import java.util.Arrays;
  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.  * Function that implements the
  27.  * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
  28.  * bicubic spline interpolation</a>. Due to numerical accuracy issues this should not
  29.  * be used.
  30.  *
  31.  * @since 2.1
  32.  * @deprecated as of 3.4 replaced by
  33.  * {@link org.apache.commons.math3.analysis.interpolation.PiecewiseBicubicSplineInterpolatingFunction}
  34.  */
  35. @Deprecated
  36. public class BicubicSplineInterpolatingFunction
  37.     implements BivariateFunction {
  38.     /** Number of coefficients. */
  39.     private static final int NUM_COEFF = 16;
  40.     /**
  41.      * Matrix to compute the spline coefficients from the function values
  42.      * and function derivatives values
  43.      */
  44.     private static final double[][] AINV = {
  45.         { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
  46.         { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
  47.         { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 },
  48.         { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 },
  49.         { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
  50.         { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 },
  51.         { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 },
  52.         { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 },
  53.         { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
  54.         { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 },
  55.         { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 },
  56.         { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 },
  57.         { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 },
  58.         { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 },
  59.         { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 },
  60.         { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 }
  61.     };

  62.     /** Samples x-coordinates */
  63.     private final double[] xval;
  64.     /** Samples y-coordinates */
  65.     private final double[] yval;
  66.     /** Set of cubic splines patching the whole data grid */
  67.     private final BicubicSplineFunction[][] splines;
  68.     /**
  69.      * Partial derivatives.
  70.      * The value of the first index determines the kind of derivatives:
  71.      * 0 = first partial derivatives wrt x
  72.      * 1 = first partial derivatives wrt y
  73.      * 2 = second partial derivatives wrt x
  74.      * 3 = second partial derivatives wrt y
  75.      * 4 = cross partial derivatives
  76.      */
  77.     private final BivariateFunction[][][] partialDerivatives;

  78.     /**
  79.      * @param x Sample values of the x-coordinate, in increasing order.
  80.      * @param y Sample values of the y-coordinate, in increasing order.
  81.      * @param f Values of the function on every grid point.
  82.      * @param dFdX Values of the partial derivative of function with respect
  83.      * to x on every grid point.
  84.      * @param dFdY Values of the partial derivative of function with respect
  85.      * to y on every grid point.
  86.      * @param d2FdXdY Values of the cross partial derivative of function on
  87.      * every grid point.
  88.      * @throws DimensionMismatchException if the various arrays do not contain
  89.      * the expected number of elements.
  90.      * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
  91.      * not strictly increasing.
  92.      * @throws NoDataException if any of the arrays has zero length.
  93.      */
  94.     public BicubicSplineInterpolatingFunction(double[] x,
  95.                                               double[] y,
  96.                                               double[][] f,
  97.                                               double[][] dFdX,
  98.                                               double[][] dFdY,
  99.                                               double[][] d2FdXdY)
  100.         throws DimensionMismatchException,
  101.                NoDataException,
  102.                NonMonotonicSequenceException {
  103.         this(x, y, f, dFdX, dFdY, d2FdXdY, false);
  104.     }

  105.     /**
  106.      * @param x Sample values of the x-coordinate, in increasing order.
  107.      * @param y Sample values of the y-coordinate, in increasing order.
  108.      * @param f Values of the function on every grid point.
  109.      * @param dFdX Values of the partial derivative of function with respect
  110.      * to x on every grid point.
  111.      * @param dFdY Values of the partial derivative of function with respect
  112.      * to y on every grid point.
  113.      * @param d2FdXdY Values of the cross partial derivative of function on
  114.      * every grid point.
  115.      * @param initializeDerivatives Whether to initialize the internal data
  116.      * needed for calling any of the methods that compute the partial derivatives
  117.      * this function.
  118.      * @throws DimensionMismatchException if the various arrays do not contain
  119.      * the expected number of elements.
  120.      * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
  121.      * not strictly increasing.
  122.      * @throws NoDataException if any of the arrays has zero length.
  123.      *
  124.      * @see #partialDerivativeX(double,double)
  125.      * @see #partialDerivativeY(double,double)
  126.      * @see #partialDerivativeXX(double,double)
  127.      * @see #partialDerivativeYY(double,double)
  128.      * @see #partialDerivativeXY(double,double)
  129.      */
  130.     public BicubicSplineInterpolatingFunction(double[] x,
  131.                                               double[] y,
  132.                                               double[][] f,
  133.                                               double[][] dFdX,
  134.                                               double[][] dFdY,
  135.                                               double[][] d2FdXdY,
  136.                                               boolean initializeDerivatives)
  137.         throws DimensionMismatchException,
  138.                NoDataException,
  139.                NonMonotonicSequenceException {
  140.         final int xLen = x.length;
  141.         final int yLen = y.length;

  142.         if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
  143.             throw new NoDataException();
  144.         }
  145.         if (xLen != f.length) {
  146.             throw new DimensionMismatchException(xLen, f.length);
  147.         }
  148.         if (xLen != dFdX.length) {
  149.             throw new DimensionMismatchException(xLen, dFdX.length);
  150.         }
  151.         if (xLen != dFdY.length) {
  152.             throw new DimensionMismatchException(xLen, dFdY.length);
  153.         }
  154.         if (xLen != d2FdXdY.length) {
  155.             throw new DimensionMismatchException(xLen, d2FdXdY.length);
  156.         }

  157.         MathArrays.checkOrder(x);
  158.         MathArrays.checkOrder(y);

  159.         xval = x.clone();
  160.         yval = y.clone();

  161.         final int lastI = xLen - 1;
  162.         final int lastJ = yLen - 1;
  163.         splines = new BicubicSplineFunction[lastI][lastJ];

  164.         for (int i = 0; i < lastI; i++) {
  165.             if (f[i].length != yLen) {
  166.                 throw new DimensionMismatchException(f[i].length, yLen);
  167.             }
  168.             if (dFdX[i].length != yLen) {
  169.                 throw new DimensionMismatchException(dFdX[i].length, yLen);
  170.             }
  171.             if (dFdY[i].length != yLen) {
  172.                 throw new DimensionMismatchException(dFdY[i].length, yLen);
  173.             }
  174.             if (d2FdXdY[i].length != yLen) {
  175.                 throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
  176.             }
  177.             final int ip1 = i + 1;
  178.             for (int j = 0; j < lastJ; j++) {
  179.                 final int jp1 = j + 1;
  180.                 final double[] beta = new double[] {
  181.                     f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1],
  182.                     dFdX[i][j], dFdX[ip1][j], dFdX[i][jp1], dFdX[ip1][jp1],
  183.                     dFdY[i][j], dFdY[ip1][j], dFdY[i][jp1], dFdY[ip1][jp1],
  184.                     d2FdXdY[i][j], d2FdXdY[ip1][j], d2FdXdY[i][jp1], d2FdXdY[ip1][jp1]
  185.                 };

  186.                 splines[i][j] = new BicubicSplineFunction(computeSplineCoefficients(beta),
  187.                                                           initializeDerivatives);
  188.             }
  189.         }

  190.         if (initializeDerivatives) {
  191.             // Compute all partial derivatives.
  192.             partialDerivatives = new BivariateFunction[5][lastI][lastJ];

  193.             for (int i = 0; i < lastI; i++) {
  194.                 for (int j = 0; j < lastJ; j++) {
  195.                     final BicubicSplineFunction bcs = splines[i][j];
  196.                     partialDerivatives[0][i][j] = bcs.partialDerivativeX();
  197.                     partialDerivatives[1][i][j] = bcs.partialDerivativeY();
  198.                     partialDerivatives[2][i][j] = bcs.partialDerivativeXX();
  199.                     partialDerivatives[3][i][j] = bcs.partialDerivativeYY();
  200.                     partialDerivatives[4][i][j] = bcs.partialDerivativeXY();
  201.                 }
  202.             }
  203.         } else {
  204.             // Partial derivative methods cannot be used.
  205.             partialDerivatives = null;
  206.         }
  207.     }

  208.     /**
  209.      * {@inheritDoc}
  210.      */
  211.     public double value(double x, double y)
  212.         throws OutOfRangeException {
  213.         final int i = searchIndex(x, xval);
  214.         final int j = searchIndex(y, yval);

  215.         final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
  216.         final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);

  217.         return splines[i][j].value(xN, yN);
  218.     }

  219.     /**
  220.      * Indicates whether a point is within the interpolation range.
  221.      *
  222.      * @param x First coordinate.
  223.      * @param y Second coordinate.
  224.      * @return {@code true} if (x, y) is a valid point.
  225.      * @since 3.3
  226.      */
  227.     public boolean isValidPoint(double x, double y) {
  228.         if (x < xval[0] ||
  229.             x > xval[xval.length - 1] ||
  230.             y < yval[0] ||
  231.             y > yval[yval.length - 1]) {
  232.             return false;
  233.         } else {
  234.             return true;
  235.         }
  236.     }

  237.     /**
  238.      * @param x x-coordinate.
  239.      * @param y y-coordinate.
  240.      * @return the value at point (x, y) of the first partial derivative with
  241.      * respect to x.
  242.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  243.      * the range defined by the boundary values of {@code xval} (resp.
  244.      * {@code yval}).
  245.      * @throws NullPointerException if the internal data were not initialized
  246.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  247.      *             double[][],double[][],double[][],boolean) constructor}).
  248.      */
  249.     public double partialDerivativeX(double x, double y)
  250.         throws OutOfRangeException {
  251.         return partialDerivative(0, x, y);
  252.     }
  253.     /**
  254.      * @param x x-coordinate.
  255.      * @param y y-coordinate.
  256.      * @return the value at point (x, y) of the first partial derivative with
  257.      * respect to y.
  258.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  259.      * the range defined by the boundary values of {@code xval} (resp.
  260.      * {@code yval}).
  261.      * @throws NullPointerException if the internal data were not initialized
  262.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  263.      *             double[][],double[][],double[][],boolean) constructor}).
  264.      */
  265.     public double partialDerivativeY(double x, double y)
  266.         throws OutOfRangeException {
  267.         return partialDerivative(1, x, y);
  268.     }
  269.     /**
  270.      * @param x x-coordinate.
  271.      * @param y y-coordinate.
  272.      * @return the value at point (x, y) of the second partial derivative with
  273.      * respect to x.
  274.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  275.      * the range defined by the boundary values of {@code xval} (resp.
  276.      * {@code yval}).
  277.      * @throws NullPointerException if the internal data were not initialized
  278.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  279.      *             double[][],double[][],double[][],boolean) constructor}).
  280.      */
  281.     public double partialDerivativeXX(double x, double y)
  282.         throws OutOfRangeException {
  283.         return partialDerivative(2, x, y);
  284.     }
  285.     /**
  286.      * @param x x-coordinate.
  287.      * @param y y-coordinate.
  288.      * @return the value at point (x, y) of the second partial derivative with
  289.      * respect to y.
  290.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  291.      * the range defined by the boundary values of {@code xval} (resp.
  292.      * {@code yval}).
  293.      * @throws NullPointerException if the internal data were not initialized
  294.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  295.      *             double[][],double[][],double[][],boolean) constructor}).
  296.      */
  297.     public double partialDerivativeYY(double x, double y)
  298.         throws OutOfRangeException {
  299.         return partialDerivative(3, x, y);
  300.     }
  301.     /**
  302.      * @param x x-coordinate.
  303.      * @param y y-coordinate.
  304.      * @return the value at point (x, y) of the second partial cross-derivative.
  305.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  306.      * the range defined by the boundary values of {@code xval} (resp.
  307.      * {@code yval}).
  308.      * @throws NullPointerException if the internal data were not initialized
  309.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  310.      *             double[][],double[][],double[][],boolean) constructor}).
  311.      */
  312.     public double partialDerivativeXY(double x, double y)
  313.         throws OutOfRangeException {
  314.         return partialDerivative(4, x, y);
  315.     }

  316.     /**
  317.      * @param which First index in {@link #partialDerivatives}.
  318.      * @param x x-coordinate.
  319.      * @param y y-coordinate.
  320.      * @return the value at point (x, y) of the selected partial derivative.
  321.      * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
  322.      * the range defined by the boundary values of {@code xval} (resp.
  323.      * {@code yval}).
  324.      * @throws NullPointerException if the internal data were not initialized
  325.      * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
  326.      *             double[][],double[][],double[][],boolean) constructor}).
  327.      */
  328.     private double partialDerivative(int which, double x, double y)
  329.         throws OutOfRangeException {
  330.         final int i = searchIndex(x, xval);
  331.         final int j = searchIndex(y, yval);

  332.         final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
  333.         final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);

  334.         return partialDerivatives[which][i][j].value(xN, yN);
  335.     }

  336.     /**
  337.      * @param c Coordinate.
  338.      * @param val Coordinate samples.
  339.      * @return the index in {@code val} corresponding to the interval
  340.      * containing {@code c}.
  341.      * @throws OutOfRangeException if {@code c} is out of the
  342.      * range defined by the boundary values of {@code val}.
  343.      */
  344.     private int searchIndex(double c, double[] val) {
  345.         final int r = Arrays.binarySearch(val, c);

  346.         if (r == -1 ||
  347.             r == -val.length - 1) {
  348.             throw new OutOfRangeException(c, val[0], val[val.length - 1]);
  349.         }

  350.         if (r < 0) {
  351.             // "c" in within an interpolation sub-interval: Return the
  352.             // index of the sample at the lower end of the sub-interval.
  353.             return -r - 2;
  354.         }
  355.         final int last = val.length - 1;
  356.         if (r == last) {
  357.             // "c" is the last sample of the range: Return the index
  358.             // of the sample at the lower end of the last sub-interval.
  359.             return last - 1;
  360.         }

  361.         // "c" is another sample point.
  362.         return r;
  363.     }

  364.     /**
  365.      * Compute the spline coefficients from the list of function values and
  366.      * function partial derivatives values at the four corners of a grid
  367.      * element. They must be specified in the following order:
  368.      * <ul>
  369.      *  <li>f(0,0)</li>
  370.      *  <li>f(1,0)</li>
  371.      *  <li>f(0,1)</li>
  372.      *  <li>f(1,1)</li>
  373.      *  <li>f<sub>x</sub>(0,0)</li>
  374.      *  <li>f<sub>x</sub>(1,0)</li>
  375.      *  <li>f<sub>x</sub>(0,1)</li>
  376.      *  <li>f<sub>x</sub>(1,1)</li>
  377.      *  <li>f<sub>y</sub>(0,0)</li>
  378.      *  <li>f<sub>y</sub>(1,0)</li>
  379.      *  <li>f<sub>y</sub>(0,1)</li>
  380.      *  <li>f<sub>y</sub>(1,1)</li>
  381.      *  <li>f<sub>xy</sub>(0,0)</li>
  382.      *  <li>f<sub>xy</sub>(1,0)</li>
  383.      *  <li>f<sub>xy</sub>(0,1)</li>
  384.      *  <li>f<sub>xy</sub>(1,1)</li>
  385.      * </ul>
  386.      * where the subscripts indicate the partial derivative with respect to
  387.      * the corresponding variable(s).
  388.      *
  389.      * @param beta List of function values and function partial derivatives
  390.      * values.
  391.      * @return the spline coefficients.
  392.      */
  393.     private double[] computeSplineCoefficients(double[] beta) {
  394.         final double[] a = new double[NUM_COEFF];

  395.         for (int i = 0; i < NUM_COEFF; i++) {
  396.             double result = 0;
  397.             final double[] row = AINV[i];
  398.             for (int j = 0; j < NUM_COEFF; j++) {
  399.                 result += row[j] * beta[j];
  400.             }
  401.             a[i] = result;
  402.         }

  403.         return a;
  404.     }
  405. }

  406. /**
  407.  * 2D-spline function.
  408.  *
  409.  */
  410. class BicubicSplineFunction implements BivariateFunction {
  411.     /** Number of points. */
  412.     private static final short N = 4;
  413.     /** Coefficients */
  414.     private final double[][] a;
  415.     /** First partial derivative along x. */
  416.     private final BivariateFunction partialDerivativeX;
  417.     /** First partial derivative along y. */
  418.     private final BivariateFunction partialDerivativeY;
  419.     /** Second partial derivative along x. */
  420.     private final BivariateFunction partialDerivativeXX;
  421.     /** Second partial derivative along y. */
  422.     private final BivariateFunction partialDerivativeYY;
  423.     /** Second crossed partial derivative. */
  424.     private final BivariateFunction partialDerivativeXY;

  425.     /**
  426.      * Simple constructor.
  427.      *
  428.      * @param coeff Spline coefficients.
  429.      */
  430.     public BicubicSplineFunction(double[] coeff) {
  431.         this(coeff, false);
  432.     }

  433.     /**
  434.      * Simple constructor.
  435.      *
  436.      * @param coeff Spline coefficients.
  437.      * @param initializeDerivatives Whether to initialize the internal data
  438.      * needed for calling any of the methods that compute the partial derivatives
  439.      * this function.
  440.      */
  441.     public BicubicSplineFunction(double[] coeff,
  442.                                  boolean initializeDerivatives) {
  443.         a = new double[N][N];
  444.         for (int i = 0; i < N; i++) {
  445.             for (int j = 0; j < N; j++) {
  446.                 a[i][j] = coeff[i * N + j];
  447.             }
  448.         }

  449.         if (initializeDerivatives) {
  450.             // Compute all partial derivatives functions.
  451.             final double[][] aX = new double[N][N];
  452.             final double[][] aY = new double[N][N];
  453.             final double[][] aXX = new double[N][N];
  454.             final double[][] aYY = new double[N][N];
  455.             final double[][] aXY = new double[N][N];

  456.             for (int i = 0; i < N; i++) {
  457.                 for (int j = 0; j < N; j++) {
  458.                     final double c = a[i][j];
  459.                     aX[i][j] = i * c;
  460.                     aY[i][j] = j * c;
  461.                     aXX[i][j] = (i - 1) * aX[i][j];
  462.                     aYY[i][j] = (j - 1) * aY[i][j];
  463.                     aXY[i][j] = j * aX[i][j];
  464.                 }
  465.             }

  466.             partialDerivativeX = new BivariateFunction() {
  467.                     public double value(double x, double y)  {
  468.                         final double x2 = x * x;
  469.                         final double[] pX = {0, 1, x, x2};

  470.                         final double y2 = y * y;
  471.                         final double y3 = y2 * y;
  472.                         final double[] pY = {1, y, y2, y3};

  473.                         return apply(pX, pY, aX);
  474.                     }
  475.                 };
  476.             partialDerivativeY = new BivariateFunction() {
  477.                     public double value(double x, double y)  {
  478.                         final double x2 = x * x;
  479.                         final double x3 = x2 * x;
  480.                         final double[] pX = {1, x, x2, x3};

  481.                         final double y2 = y * y;
  482.                         final double[] pY = {0, 1, y, y2};

  483.                         return apply(pX, pY, aY);
  484.                     }
  485.                 };
  486.             partialDerivativeXX = new BivariateFunction() {
  487.                     public double value(double x, double y)  {
  488.                         final double[] pX = {0, 0, 1, x};

  489.                         final double y2 = y * y;
  490.                         final double y3 = y2 * y;
  491.                         final double[] pY = {1, y, y2, y3};

  492.                         return apply(pX, pY, aXX);
  493.                     }
  494.                 };
  495.             partialDerivativeYY = new BivariateFunction() {
  496.                     public double value(double x, double y)  {
  497.                         final double x2 = x * x;
  498.                         final double x3 = x2 * x;
  499.                         final double[] pX = {1, x, x2, x3};

  500.                         final double[] pY = {0, 0, 1, y};

  501.                         return apply(pX, pY, aYY);
  502.                     }
  503.                 };
  504.             partialDerivativeXY = new BivariateFunction() {
  505.                     public double value(double x, double y)  {
  506.                         final double x2 = x * x;
  507.                         final double[] pX = {0, 1, x, x2};

  508.                         final double y2 = y * y;
  509.                         final double[] pY = {0, 1, y, y2};

  510.                         return apply(pX, pY, aXY);
  511.                     }
  512.                 };
  513.         } else {
  514.             partialDerivativeX = null;
  515.             partialDerivativeY = null;
  516.             partialDerivativeXX = null;
  517.             partialDerivativeYY = null;
  518.             partialDerivativeXY = null;
  519.         }
  520.     }

  521.     /**
  522.      * {@inheritDoc}
  523.      */
  524.     public double value(double x, double y) {
  525.         if (x < 0 || x > 1) {
  526.             throw new OutOfRangeException(x, 0, 1);
  527.         }
  528.         if (y < 0 || y > 1) {
  529.             throw new OutOfRangeException(y, 0, 1);
  530.         }

  531.         final double x2 = x * x;
  532.         final double x3 = x2 * x;
  533.         final double[] pX = {1, x, x2, x3};

  534.         final double y2 = y * y;
  535.         final double y3 = y2 * y;
  536.         final double[] pY = {1, y, y2, y3};

  537.         return apply(pX, pY, a);
  538.     }

  539.     /**
  540.      * Compute the value of the bicubic polynomial.
  541.      *
  542.      * @param pX Powers of the x-coordinate.
  543.      * @param pY Powers of the y-coordinate.
  544.      * @param coeff Spline coefficients.
  545.      * @return the interpolated value.
  546.      */
  547.     private double apply(double[] pX, double[] pY, double[][] coeff) {
  548.         double result = 0;
  549.         for (int i = 0; i < N; i++) {
  550.             for (int j = 0; j < N; j++) {
  551.                 result += coeff[i][j] * pX[i] * pY[j];
  552.             }
  553.         }

  554.         return result;
  555.     }

  556.     /**
  557.      * @return the partial derivative wrt {@code x}.
  558.      */
  559.     public BivariateFunction partialDerivativeX() {
  560.         return partialDerivativeX;
  561.     }
  562.     /**
  563.      * @return the partial derivative wrt {@code y}.
  564.      */
  565.     public BivariateFunction partialDerivativeY() {
  566.         return partialDerivativeY;
  567.     }
  568.     /**
  569.      * @return the second partial derivative wrt {@code x}.
  570.      */
  571.     public BivariateFunction partialDerivativeXX() {
  572.         return partialDerivativeXX;
  573.     }
  574.     /**
  575.      * @return the second partial derivative wrt {@code y}.
  576.      */
  577.     public BivariateFunction partialDerivativeYY() {
  578.         return partialDerivativeYY;
  579.     }
  580.     /**
  581.      * @return the second partial cross-derivative.
  582.      */
  583.     public BivariateFunction partialDerivativeXY() {
  584.         return partialDerivativeXY;
  585.     }
  586. }