PiecewiseBicubicSplineInterpolatingFunction.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This is not the original file distributed by the Apache Software Foundation
 * It has been modified by the Hipparchus project
 */
package org.hipparchus.analysis.interpolation;

import java.util.Arrays;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.analysis.BivariateFunction;
import org.hipparchus.analysis.FieldBivariateFunction;
import org.hipparchus.analysis.polynomials.FieldPolynomialSplineFunction;
import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.exception.NullArgumentException;
import org.hipparchus.util.MathArrays;

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

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

    /** Simple constructor.
     * @param x Sample values of the x-coordinate, in increasing order.
     * @param y Sample values of the y-coordinate, in increasing order.
     * @param f Values of the function on every grid point. the expected number
     *        of elements.
     * @throws MathIllegalArgumentException if {@code x} or {@code y} are not
     *         strictly increasing.
     * @throws NullArgumentException if any of the arguments are null
     * @throws MathIllegalArgumentException if any of the arrays has zero length.
     * @throws MathIllegalArgumentException if the length of x and y don't match the row, column
     *         height of f
     */
    public PiecewiseBicubicSplineInterpolatingFunction(double[] x,
                                                       double[] y,
                                                       double[][] f)
        throws MathIllegalArgumentException, NullArgumentException {
        if (x == null ||
            y == null ||
            f == null ||
            f[0] == null) {
            throw new NullArgumentException();
        }

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

        if (xLen == 0 ||
            yLen == 0 ||
            f.length == 0 ||
            f[0].length == 0) {
            throw new MathIllegalArgumentException(LocalizedCoreFormats.NO_DATA);
        }

        if (xLen < MIN_NUM_POINTS ||
            yLen < MIN_NUM_POINTS ||
            f.length < MIN_NUM_POINTS ||
            f[0].length < MIN_NUM_POINTS) {
            throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DATA);
        }

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

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

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

        xval = x.clone();
        yval = y.clone();
        fval = f.clone();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double value(double x,
                        double y)
        throws MathIllegalArgumentException {
        final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
        final int offset = 2;
        final int count = offset + 3;
        final int i = searchIndex(x, xval, offset, count);
        final int j = searchIndex(y, yval, offset, count);

        final double[] xArray = new double[count];
        final double[] yArray = new double[count];
        final double[] zArray = new double[count];
        final double[] interpArray = new double[count];

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

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

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

        return spline.value(y);

    }

    /**
     * {@inheritDoc}
     * @since 1.5
     */
    @Override
    public <T extends CalculusFieldElement<T>> T value(final T x, final T y)
        throws MathIllegalArgumentException {
        final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
        final int offset = 2;
        final int count = offset + 3;
        final int i = searchIndex(x.getReal(), xval, offset, count);
        final int j = searchIndex(y.getReal(), yval, offset, count);

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

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

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

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

        return spline.value(y);

    }

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

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

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

        if (r < 0) {
            // "c" in within an interpolation sub-interval, which returns
            // negative
            // need to remove the negative sign for consistency
            r = -r - offset - 1;
        } else {
            r -= offset;
        }

        if (r < 0) {
            r = 0;
        }

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

        return r;
    }
}