PiecewiseBicubicSplineInterpolatingFunction.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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.interpolation;

  22. import java.util.Arrays;

  23. import org.hipparchus.CalculusFieldElement;
  24. import org.hipparchus.analysis.BivariateFunction;
  25. import org.hipparchus.analysis.FieldBivariateFunction;
  26. import org.hipparchus.analysis.polynomials.FieldPolynomialSplineFunction;
  27. import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
  28. import org.hipparchus.exception.LocalizedCoreFormats;
  29. import org.hipparchus.exception.MathIllegalArgumentException;
  30. import org.hipparchus.exception.NullArgumentException;
  31. import org.hipparchus.util.MathArrays;

  32. /**
  33.  * Function that implements the
  34.  * <a href="http://www.paulinternet.nl/?page=bicubic">bicubic spline</a>
  35.  * interpolation.
  36.  * This implementation currently uses {@link AkimaSplineInterpolator} as the
  37.  * underlying one-dimensional interpolator, which requires 5 sample points;
  38.  * insufficient data will raise an exception when the
  39.  * {@link #value(double,double) value} method is called.
  40.  *
  41.  */
  42. public class PiecewiseBicubicSplineInterpolatingFunction
  43.     implements BivariateFunction, FieldBivariateFunction {

  44.     /** The minimum number of points that are needed to compute the function. */
  45.     private static final int MIN_NUM_POINTS = 5;
  46.     /** Samples x-coordinates */
  47.     private final double[] xval;
  48.     /** Samples y-coordinates */
  49.     private final double[] yval;
  50.     /** Set of cubic splines patching the whole data grid */
  51.     private final double[][] fval;

  52.     /** Simple constructor.
  53.      * @param x Sample values of the x-coordinate, in increasing order.
  54.      * @param y Sample values of the y-coordinate, in increasing order.
  55.      * @param f Values of the function on every grid point. the expected number
  56.      *        of elements.
  57.      * @throws MathIllegalArgumentException if {@code x} or {@code y} are not
  58.      *         strictly increasing.
  59.      * @throws NullArgumentException if any of the arguments are null
  60.      * @throws MathIllegalArgumentException if any of the arrays has zero length.
  61.      * @throws MathIllegalArgumentException if the length of x and y don't match the row, column
  62.      *         height of f
  63.      */
  64.     public PiecewiseBicubicSplineInterpolatingFunction(double[] x,
  65.                                                        double[] y,
  66.                                                        double[][] f)
  67.         throws MathIllegalArgumentException, NullArgumentException {
  68.         if (x == null ||
  69.             y == null ||
  70.             f == null ||
  71.             f[0] == null) {
  72.             throw new NullArgumentException();
  73.         }

  74.         final int xLen = x.length;
  75.         final int yLen = y.length;

  76.         if (xLen == 0 ||
  77.             yLen == 0 ||
  78.             f.length == 0 ||
  79.             f[0].length == 0) {
  80.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NO_DATA);
  81.         }

  82.         if (xLen < MIN_NUM_POINTS ||
  83.             yLen < MIN_NUM_POINTS ||
  84.             f.length < MIN_NUM_POINTS ||
  85.             f[0].length < MIN_NUM_POINTS) {
  86.             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DATA);
  87.         }

  88.         if (xLen != f.length) {
  89.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  90.                                                    xLen, f.length);
  91.         }

  92.         if (yLen != f[0].length) {
  93.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  94.                                                    yLen, f[0].length);
  95.         }

  96.         MathArrays.checkOrder(x);
  97.         MathArrays.checkOrder(y);

  98.         xval = x.clone();
  99.         yval = y.clone();
  100.         fval = f.clone();
  101.     }

  102.     /**
  103.      * {@inheritDoc}
  104.      */
  105.     @Override
  106.     public double value(double x,
  107.                         double y)
  108.         throws MathIllegalArgumentException {
  109.         final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
  110.         final int offset = 2;
  111.         final int count = offset + 3;
  112.         final int i = searchIndex(x, xval, offset, count);
  113.         final int j = searchIndex(y, yval, offset, count);

  114.         final double[] xArray = new double[count];
  115.         final double[] yArray = new double[count];
  116.         final double[] zArray = new double[count];
  117.         final double[] interpArray = new double[count];

  118.         for (int index = 0; index < count; index++) {
  119.             xArray[index] = xval[i + index];
  120.             yArray[index] = yval[j + index];
  121.         }

  122.         for (int zIndex = 0; zIndex < count; zIndex++) {
  123.             for (int index = 0; index < count; index++) {
  124.                 zArray[index] = fval[i + index][j + zIndex];
  125.             }
  126.             final PolynomialSplineFunction spline = interpolator.interpolate(xArray, zArray);
  127.             interpArray[zIndex] = spline.value(x);
  128.         }

  129.         final PolynomialSplineFunction spline = interpolator.interpolate(yArray, interpArray);

  130.         return spline.value(y);

  131.     }

  132.     /**
  133.      * {@inheritDoc}
  134.      * @since 1.5
  135.      */
  136.     @Override
  137.     public <T extends CalculusFieldElement<T>> T value(final T x, final T y)
  138.         throws MathIllegalArgumentException {
  139.         final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
  140.         final int offset = 2;
  141.         final int count = offset + 3;
  142.         final int i = searchIndex(x.getReal(), xval, offset, count);
  143.         final int j = searchIndex(y.getReal(), yval, offset, count);

  144.         final double[] xArray = new double[count];
  145.         final T[] yArray = MathArrays.buildArray(x.getField(), count);
  146.         final double[] zArray = new double[count];
  147.         final T[] interpArray = MathArrays.buildArray(x.getField(), count);

  148.         final T zero = x.getField().getZero();
  149.         for (int index = 0; index < count; index++) {
  150.             xArray[index] = xval[i + index];
  151.             yArray[index] = zero.add(yval[j + index]);
  152.         }

  153.         for (int zIndex = 0; zIndex < count; zIndex++) {
  154.             for (int index = 0; index < count; index++) {
  155.                 zArray[index] = fval[i + index][j + zIndex];
  156.             }
  157.             final PolynomialSplineFunction spline = interpolator.interpolate(xArray, zArray);
  158.             interpArray[zIndex] = spline.value(x);
  159.         }

  160.         final FieldPolynomialSplineFunction<T> spline = interpolator.interpolate(yArray, interpArray);

  161.         return spline.value(y);

  162.     }

  163.     /**
  164.      * Indicates whether a point is within the interpolation range.
  165.      *
  166.      * @param x First coordinate.
  167.      * @param y Second coordinate.
  168.      * @return {@code true} if (x, y) is a valid point.
  169.      */
  170.     public boolean isValidPoint(double x,
  171.                                 double y) {
  172.         return x >= xval[0] && x <= xval[xval.length - 1] && y >= yval[0] && y <= yval[yval.length - 1];
  173.     }

  174.     /**
  175.      * @param c Coordinate.
  176.      * @param val Coordinate samples.
  177.      * @param offset how far back from found value to offset for querying
  178.      * @param count total number of elements forward from beginning that will be
  179.      *        queried
  180.      * @return the index in {@code val} corresponding to the interval containing
  181.      *         {@code c}.
  182.      * @throws MathIllegalArgumentException if {@code c} is out of the range defined by
  183.      *         the boundary values of {@code val}.
  184.      */
  185.     private int searchIndex(double c,
  186.                             double[] val,
  187.                             int offset,
  188.                             int count) {
  189.         int r = Arrays.binarySearch(val, c);

  190.         if (r == -1 || r == -val.length - 1) {
  191.             throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
  192.                                                    c, val[0], val[val.length - 1]);
  193.         }

  194.         if (r < 0) {
  195.             // "c" in within an interpolation sub-interval, which returns
  196.             // negative
  197.             // need to remove the negative sign for consistency
  198.             r = -r - offset - 1;
  199.         } else {
  200.             r -= offset;
  201.         }

  202.         if (r < 0) {
  203.             r = 0;
  204.         }

  205.         if ((r + count) >= val.length) {
  206.             // "c" is the last sample of the range: Return the index
  207.             // of the sample at the lower end of the last sub-interval.
  208.             r = val.length - count;
  209.         }

  210.         return r;
  211.     }
  212. }