BicubicInterpolatingFunction.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.math4.legacy.analysis.interpolation;

  18. import java.util.Arrays;
  19. import java.util.function.DoubleBinaryOperator;
  20. import java.util.function.Function;

  21. import org.apache.commons.numbers.core.Sum;
  22. import org.apache.commons.math4.legacy.analysis.BivariateFunction;
  23. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  24. import org.apache.commons.math4.legacy.exception.NoDataException;
  25. import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
  26. import org.apache.commons.math4.legacy.exception.OutOfRangeException;
  27. import org.apache.commons.math4.legacy.core.MathArrays;

  28. /**
  29.  * Function that implements the
  30.  * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
  31.  * bicubic spline interpolation</a>.
  32.  *
  33.  * @since 3.4
  34.  */
  35. public class BicubicInterpolatingFunction
  36.     implements BivariateFunction {
  37.     /** Number of coefficients. */
  38.     private static final int NUM_COEFF = 16;
  39.     /**
  40.      * Matrix to compute the spline coefficients from the function values
  41.      * and function derivatives values.
  42.      */
  43.     private static final double[][] AINV = {
  44.         { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
  45.         { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
  46.         { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 },
  47.         { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 },
  48.         { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
  49.         { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 },
  50.         { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 },
  51.         { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 },
  52.         { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
  53.         { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 },
  54.         { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 },
  55.         { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 },
  56.         { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 },
  57.         { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 },
  58.         { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 },
  59.         { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 }
  60.     };

  61.     /** Samples x-coordinates. */
  62.     private final double[] xval;
  63.     /** Samples y-coordinates. */
  64.     private final double[] yval;
  65.     /** Set of cubic splines patching the whole data grid. */
  66.     private final BicubicFunction[][] splines;

  67.     /**
  68.      * @param x Sample values of the x-coordinate, in increasing order.
  69.      * @param y Sample values of the y-coordinate, in increasing order.
  70.      * @param f Values of the function on every grid point.
  71.      * @param dFdX Values of the partial derivative of function with respect
  72.      * to x on every grid point.
  73.      * @param dFdY Values of the partial derivative of function with respect
  74.      * to y on every grid point.
  75.      * @param d2FdXdY Values of the cross partial derivative of function on
  76.      * every grid point.
  77.      * @throws DimensionMismatchException if the various arrays do not contain
  78.      * the expected number of elements.
  79.      * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
  80.      * not strictly increasing.
  81.      * @throws NoDataException if any of the arrays has zero length.
  82.      */
  83.     public BicubicInterpolatingFunction(double[] x,
  84.                                         double[] y,
  85.                                         double[][] f,
  86.                                         double[][] dFdX,
  87.                                         double[][] dFdY,
  88.                                         double[][] d2FdXdY)
  89.         throws DimensionMismatchException,
  90.                NoDataException,
  91.                NonMonotonicSequenceException {
  92.         this(x, y, f, dFdX, dFdY, d2FdXdY, false);
  93.     }

  94.     /**
  95.      * @param x Sample values of the x-coordinate, in increasing order.
  96.      * @param y Sample values of the y-coordinate, in increasing order.
  97.      * @param f Values of the function on every grid point.
  98.      * @param dFdX Values of the partial derivative of function with respect
  99.      * to x on every grid point.
  100.      * @param dFdY Values of the partial derivative of function with respect
  101.      * to y on every grid point.
  102.      * @param d2FdXdY Values of the cross partial derivative of function on
  103.      * every grid point.
  104.      * @param initializeDerivatives Whether to initialize the internal data
  105.      * needed for calling any of the methods that compute the partial derivatives
  106.      * this function.
  107.      * @throws DimensionMismatchException if the various arrays do not contain
  108.      * the expected number of elements.
  109.      * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
  110.      * not strictly increasing.
  111.      * @throws NoDataException if any of the arrays has zero length.
  112.      */
  113.     public BicubicInterpolatingFunction(double[] x,
  114.                                         double[] y,
  115.                                         double[][] f,
  116.                                         double[][] dFdX,
  117.                                         double[][] dFdY,
  118.                                         double[][] d2FdXdY,
  119.                                         boolean initializeDerivatives)
  120.         throws DimensionMismatchException,
  121.                NoDataException,
  122.                NonMonotonicSequenceException {
  123.         final int xLen = x.length;
  124.         final int yLen = y.length;

  125.         if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
  126.             throw new NoDataException();
  127.         }
  128.         if (xLen != f.length) {
  129.             throw new DimensionMismatchException(xLen, f.length);
  130.         }
  131.         if (xLen != dFdX.length) {
  132.             throw new DimensionMismatchException(xLen, dFdX.length);
  133.         }
  134.         if (xLen != dFdY.length) {
  135.             throw new DimensionMismatchException(xLen, dFdY.length);
  136.         }
  137.         if (xLen != d2FdXdY.length) {
  138.             throw new DimensionMismatchException(xLen, d2FdXdY.length);
  139.         }

  140.         MathArrays.checkOrder(x);
  141.         MathArrays.checkOrder(y);

  142.         xval = x.clone();
  143.         yval = y.clone();

  144.         final int lastI = xLen - 1;
  145.         final int lastJ = yLen - 1;
  146.         splines = new BicubicFunction[lastI][lastJ];

  147.         for (int i = 0; i < lastI; i++) {
  148.             if (f[i].length != yLen) {
  149.                 throw new DimensionMismatchException(f[i].length, yLen);
  150.             }
  151.             if (dFdX[i].length != yLen) {
  152.                 throw new DimensionMismatchException(dFdX[i].length, yLen);
  153.             }
  154.             if (dFdY[i].length != yLen) {
  155.                 throw new DimensionMismatchException(dFdY[i].length, yLen);
  156.             }
  157.             if (d2FdXdY[i].length != yLen) {
  158.                 throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
  159.             }
  160.             final int ip1 = i + 1;
  161.             final double xR = xval[ip1] - xval[i];
  162.             for (int j = 0; j < lastJ; j++) {
  163.                 final int jp1 = j + 1;
  164.                 final double yR = yval[jp1] - yval[j];
  165.                 final double xRyR = xR * yR;
  166.                 final double[] beta = new double[] {
  167.                     f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1],
  168.                     dFdX[i][j] * xR, dFdX[ip1][j] * xR, dFdX[i][jp1] * xR, dFdX[ip1][jp1] * xR,
  169.                     dFdY[i][j] * yR, dFdY[ip1][j] * yR, dFdY[i][jp1] * yR, dFdY[ip1][jp1] * yR,
  170.                     d2FdXdY[i][j] * xRyR, d2FdXdY[ip1][j] * xRyR, d2FdXdY[i][jp1] * xRyR, d2FdXdY[ip1][jp1] * xRyR
  171.                 };

  172.                 splines[i][j] = new BicubicFunction(computeSplineCoefficients(beta),
  173.                                                     xR,
  174.                                                     yR,
  175.                                                     initializeDerivatives);
  176.             }
  177.         }
  178.     }

  179.     /**
  180.      * {@inheritDoc}
  181.      */
  182.     @Override
  183.     public double value(double x, double y)
  184.         throws OutOfRangeException {
  185.         final int i = searchIndex(x, xval);
  186.         final int j = searchIndex(y, yval);

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

  189.         return splines[i][j].value(xN, yN);
  190.     }

  191.     /**
  192.      * Indicates whether a point is within the interpolation range.
  193.      *
  194.      * @param x First coordinate.
  195.      * @param y Second coordinate.
  196.      * @return {@code true} if (x, y) is a valid point.
  197.      */
  198.     public boolean isValidPoint(double x, double y) {
  199.         return !(x < xval[0] ||
  200.             x > xval[xval.length - 1] ||
  201.             y < yval[0] ||
  202.             y > yval[yval.length - 1]);
  203.     }

  204.     /**
  205.      * @return the first partial derivative respect to x.
  206.      * @throws NullPointerException if the internal data were not initialized
  207.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  208.      *             double[][],double[][],double[][],boolean) constructor}).
  209.      */
  210.     public DoubleBinaryOperator partialDerivativeX() {
  211.         return partialDerivative(BicubicFunction::partialDerivativeX);
  212.     }

  213.     /**
  214.      * @return the first partial derivative respect to y.
  215.      * @throws NullPointerException if the internal data were not initialized
  216.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  217.      *             double[][],double[][],double[][],boolean) constructor}).
  218.      */
  219.     public DoubleBinaryOperator partialDerivativeY() {
  220.         return partialDerivative(BicubicFunction::partialDerivativeY);
  221.     }

  222.     /**
  223.      * @return the second partial derivative respect to x.
  224.      * @throws NullPointerException if the internal data were not initialized
  225.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  226.      *             double[][],double[][],double[][],boolean) constructor}).
  227.      */
  228.     public DoubleBinaryOperator partialDerivativeXX() {
  229.         return partialDerivative(BicubicFunction::partialDerivativeXX);
  230.     }

  231.     /**
  232.      * @return the second partial derivative respect to y.
  233.      * @throws NullPointerException if the internal data were not initialized
  234.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  235.      *             double[][],double[][],double[][],boolean) constructor}).
  236.      */
  237.     public DoubleBinaryOperator partialDerivativeYY() {
  238.         return partialDerivative(BicubicFunction::partialDerivativeYY);
  239.     }

  240.     /**
  241.      * @return the second partial cross derivative.
  242.      * @throws NullPointerException if the internal data were not initialized
  243.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  244.      *             double[][],double[][],double[][],boolean) constructor}).
  245.      */
  246.     public DoubleBinaryOperator partialDerivativeXY() {
  247.         return partialDerivative(BicubicFunction::partialDerivativeXY);
  248.     }

  249.     /**
  250.      * @param which derivative function to apply.
  251.      * @return the selected partial derivative.
  252.      * @throws NullPointerException if the internal data were not initialized
  253.      * (cf. {@link #BicubicInterpolatingFunction(double[],double[],double[][],
  254.      *             double[][],double[][],double[][],boolean) constructor}).
  255.      */
  256.     private DoubleBinaryOperator partialDerivative(Function<BicubicFunction, BivariateFunction> which) {
  257.         return (x, y) -> {
  258.             final int i = searchIndex(x, xval);
  259.             final int j = searchIndex(y, yval);

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

  262.             return which.apply(splines[i][j]).value(xN, yN);
  263.         };
  264.     }

  265.     /**
  266.      * @param c Coordinate.
  267.      * @param val Coordinate samples.
  268.      * @return the index in {@code val} corresponding to the interval
  269.      * containing {@code c}.
  270.      * @throws OutOfRangeException if {@code c} is out of the
  271.      * range defined by the boundary values of {@code val}.
  272.      */
  273.     private static int searchIndex(double c, double[] val) {
  274.         final int r = Arrays.binarySearch(val, c);

  275.         if (r == -1 ||
  276.             r == -val.length - 1) {
  277.             throw new OutOfRangeException(c, val[0], val[val.length - 1]);
  278.         }

  279.         if (r < 0) {
  280.             // "c" in within an interpolation sub-interval: Return the
  281.             // index of the sample at the lower end of the sub-interval.
  282.             return -r - 2;
  283.         }
  284.         final int last = val.length - 1;
  285.         if (r == last) {
  286.             // "c" is the last sample of the range: Return the index
  287.             // of the sample at the lower end of the last sub-interval.
  288.             return last - 1;
  289.         }

  290.         // "c" is another sample point.
  291.         return r;
  292.     }

  293.     /**
  294.      * Compute the spline coefficients from the list of function values and
  295.      * function partial derivatives values at the four corners of a grid
  296.      * element. They must be specified in the following order:
  297.      * <ul>
  298.      *  <li>f(0,0)</li>
  299.      *  <li>f(1,0)</li>
  300.      *  <li>f(0,1)</li>
  301.      *  <li>f(1,1)</li>
  302.      *  <li>f<sub>x</sub>(0,0)</li>
  303.      *  <li>f<sub>x</sub>(1,0)</li>
  304.      *  <li>f<sub>x</sub>(0,1)</li>
  305.      *  <li>f<sub>x</sub>(1,1)</li>
  306.      *  <li>f<sub>y</sub>(0,0)</li>
  307.      *  <li>f<sub>y</sub>(1,0)</li>
  308.      *  <li>f<sub>y</sub>(0,1)</li>
  309.      *  <li>f<sub>y</sub>(1,1)</li>
  310.      *  <li>f<sub>xy</sub>(0,0)</li>
  311.      *  <li>f<sub>xy</sub>(1,0)</li>
  312.      *  <li>f<sub>xy</sub>(0,1)</li>
  313.      *  <li>f<sub>xy</sub>(1,1)</li>
  314.      * </ul>
  315.      * where the subscripts indicate the partial derivative with respect to
  316.      * the corresponding variable(s).
  317.      *
  318.      * @param beta List of function values and function partial derivatives
  319.      * values.
  320.      * @return the spline coefficients.
  321.      */
  322.     private static double[] computeSplineCoefficients(double[] beta) {
  323.         final double[] a = new double[NUM_COEFF];

  324.         for (int i = 0; i < NUM_COEFF; i++) {
  325.             double result = 0;
  326.             final double[] row = AINV[i];
  327.             for (int j = 0; j < NUM_COEFF; j++) {
  328.                 result += row[j] * beta[j];
  329.             }
  330.             a[i] = result;
  331.         }

  332.         return a;
  333.     }

  334.     /**
  335.      * Bicubic function.
  336.      */
  337.     static final class BicubicFunction implements BivariateFunction {
  338.         /** Number of points. */
  339.         private static final short N = 4;
  340.         /** Coefficients. */
  341.         private final double[][] a;
  342.         /** First partial derivative along x. */
  343.         private final BivariateFunction partialDerivativeX;
  344.         /** First partial derivative along y. */
  345.         private final BivariateFunction partialDerivativeY;
  346.         /** Second partial derivative along x. */
  347.         private final BivariateFunction partialDerivativeXX;
  348.         /** Second partial derivative along y. */
  349.         private final BivariateFunction partialDerivativeYY;
  350.         /** Second crossed partial derivative. */
  351.         private final BivariateFunction partialDerivativeXY;

  352.         /**
  353.          * Simple constructor.
  354.          *
  355.          * @param coeff Spline coefficients.
  356.          * @param xR x spacing.
  357.          * @param yR y spacing.
  358.          * @param initializeDerivatives Whether to initialize the internal data
  359.          * needed for calling any of the methods that compute the partial derivatives
  360.          * this function.
  361.          */
  362.         BicubicFunction(double[] coeff,
  363.                         double xR,
  364.                         double yR,
  365.                         boolean initializeDerivatives) {
  366.             a = new double[N][N];
  367.             for (int j = 0; j < N; j++) {
  368.                 final double[] aJ = a[j];
  369.                 for (int i = 0; i < N; i++) {
  370.                     aJ[i] = coeff[i * N + j];
  371.                 }
  372.             }

  373.             if (initializeDerivatives) {
  374.                 // Compute all partial derivatives functions.
  375.                 final double[][] aX = new double[N][N];
  376.                 final double[][] aY = new double[N][N];
  377.                 final double[][] aXX = new double[N][N];
  378.                 final double[][] aYY = new double[N][N];
  379.                 final double[][] aXY = new double[N][N];

  380.                 for (int i = 0; i < N; i++) {
  381.                     for (int j = 0; j < N; j++) {
  382.                         final double c = a[i][j];
  383.                         aX[i][j] = i * c;
  384.                         aY[i][j] = j * c;
  385.                         aXX[i][j] = (i - 1) * aX[i][j];
  386.                         aYY[i][j] = (j - 1) * aY[i][j];
  387.                         aXY[i][j] = j * aX[i][j];
  388.                     }
  389.                 }

  390.                 partialDerivativeX = (double x, double y) -> {
  391.                     final double x2 = x * x;
  392.                     final double[] pX = {0, 1, x, x2};

  393.                     final double y2 = y * y;
  394.                     final double y3 = y2 * y;
  395.                     final double[] pY = {1, y, y2, y3};

  396.                     return apply(pX, 1, pY, 0, aX) / xR;
  397.                 };
  398.                 partialDerivativeY = (double x, double y) -> {
  399.                     final double x2 = x * x;
  400.                     final double x3 = x2 * x;
  401.                     final double[] pX = {1, x, x2, x3};

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

  404.                     return apply(pX, 0, pY, 1, aY) / yR;
  405.                 };
  406.                 partialDerivativeXX = (double x, double y) -> {
  407.                     final double[] pX = {0, 0, 1, x};

  408.                     final double y2 = y * y;
  409.                     final double y3 = y2 * y;
  410.                     final double[] pY = {1, y, y2, y3};

  411.                     return apply(pX, 2, pY, 0, aXX) / (xR * xR);
  412.                 };
  413.                 partialDerivativeYY = (double x, double y) -> {
  414.                     final double x2 = x * x;
  415.                     final double x3 = x2 * x;
  416.                     final double[] pX = {1, x, x2, x3};

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

  418.                     return apply(pX, 0, pY, 2, aYY) / (yR * yR);
  419.                 };
  420.                 partialDerivativeXY = (double x, double y) -> {
  421.                     final double x2 = x * x;
  422.                     final double[] pX = {0, 1, x, x2};

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

  425.                     return apply(pX, 1, pY, 1, aXY) / (xR * yR);
  426.                 };
  427.             } else {
  428.                 partialDerivativeX = null;
  429.                 partialDerivativeY = null;
  430.                 partialDerivativeXX = null;
  431.                 partialDerivativeYY = null;
  432.                 partialDerivativeXY = null;
  433.             }
  434.         }

  435.         /**
  436.          * {@inheritDoc}
  437.          */
  438.         @Override
  439.         public double value(double x, double y) {
  440.             if (x < 0 || x > 1) {
  441.                 throw new OutOfRangeException(x, 0, 1);
  442.             }
  443.             if (y < 0 || y > 1) {
  444.                 throw new OutOfRangeException(y, 0, 1);
  445.             }

  446.             final double x2 = x * x;
  447.             final double x3 = x2 * x;
  448.             final double[] pX = {1, x, x2, x3};

  449.             final double y2 = y * y;
  450.             final double y3 = y2 * y;
  451.             final double[] pY = {1, y, y2, y3};

  452.             return apply(pX, 0, pY, 0, a);
  453.         }

  454.         /**
  455.          * Compute the value of the bicubic polynomial.
  456.          *
  457.          * <p>Assumes the powers are zero below the provided index, and 1 at the provided
  458.          * index. This allows skipping some zero products and optimising multiplication
  459.          * by one.
  460.          *
  461.          * @param pX Powers of the x-coordinate.
  462.          * @param i Index of pX[i] == 1
  463.          * @param pY Powers of the y-coordinate.
  464.          * @param j Index of pX[j] == 1
  465.          * @param coeff Spline coefficients.
  466.          * @return the interpolated value.
  467.          */
  468.         private static double apply(double[] pX, int i, double[] pY, int j, double[][] coeff) {
  469.             // assert pX[i] == 1
  470.             double result = sumOfProducts(coeff[i], pY, j);
  471.             while (++i < N) {
  472.                 final double r = sumOfProducts(coeff[i], pY, j);
  473.                 result += r * pX[i];
  474.             }
  475.             return result;
  476.         }

  477.         /**
  478.          * Compute the sum of products starting from the provided index.
  479.          * Assumes that factor {@code b[j] == 1}.
  480.          *
  481.          * @param a Factors.
  482.          * @param b Factors.
  483.          * @param j Index to initialise the sum.
  484.          * @return the double
  485.          */
  486.         private static double sumOfProducts(double[] a, double[] b, int j) {
  487.             // assert b[j] == 1
  488.             final Sum sum = Sum.of(a[j]);
  489.             while (++j < N) {
  490.                 sum.addProduct(a[j], b[j]);
  491.             }
  492.             return sum.getAsDouble();
  493.         }

  494.         /**
  495.          * @return the partial derivative wrt {@code x}.
  496.          */
  497.         BivariateFunction partialDerivativeX() {
  498.             return partialDerivativeX;
  499.         }

  500.         /**
  501.          * @return the partial derivative wrt {@code y}.
  502.          */
  503.         BivariateFunction partialDerivativeY() {
  504.             return partialDerivativeY;
  505.         }

  506.         /**
  507.          * @return the second partial derivative wrt {@code x}.
  508.          */
  509.         BivariateFunction partialDerivativeXX() {
  510.             return partialDerivativeXX;
  511.         }

  512.         /**
  513.          * @return the second partial derivative wrt {@code y}.
  514.          */
  515.         BivariateFunction partialDerivativeYY() {
  516.             return partialDerivativeYY;
  517.         }

  518.         /**
  519.          * @return the second partial cross-derivative.
  520.          */
  521.         BivariateFunction partialDerivativeXY() {
  522.             return partialDerivativeXY;
  523.         }
  524.     }
  525. }