blob: 2d1520d29b79ad01aea423ee404cf01481eadfb1 [file] [log] [blame]
/*
* 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
*
* http://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.
*/
package org.apache.commons.math4.legacy.analysis.interpolation;
import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialSplineFunction;
import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
import org.apache.commons.math4.legacy.exception.NullArgumentException;
import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
import org.apache.commons.math4.legacy.core.jdkmath.AccurateMath;
import org.apache.commons.math4.legacy.core.MathArrays;
import org.apache.commons.numbers.core.Precision;
/**
* Computes a cubic spline interpolation for the data set using the Akima
* algorithm, as originally formulated by Hiroshi Akima in his 1970 paper
* "A New Method of Interpolation and Smooth Curve Fitting Based on Local Procedures."
* J. ACM 17, 4 (October 1970), 589-602. DOI=10.1145/321607.321609
* http://doi.acm.org/10.1145/321607.321609
* <p>
* This implementation is based on the Akima implementation in the CubicSpline
* class in the Math.NET Numerics library. The method referenced is
* "CubicSpline.InterpolateAkimaSorted".
* </p>
* <p>
* When the {@link #AkimaSplineInterpolator(boolean) argument to the constructor}
* is {@code true}, the weights are modified in order to
* <a href="https://nl.mathworks.com/help/matlab/ref/makima.html">smooth out
* spurious oscillations</a>.
* </p>
* <p>
* The {@link #interpolate(double[], double[]) interpolate} method returns a
* {@link PolynomialSplineFunction} consisting of n cubic polynomials, defined
* over the subintervals determined by the x values, {@code x[0] < x[i] ... < x[n]}.
* The Akima algorithm requires that {@code n >= 5}.
* </p>
*/
public class AkimaSplineInterpolator
implements UnivariateInterpolator {
/** The minimum number of points that are needed to compute the function. */
private static final int MINIMUM_NUMBER_POINTS = 5;
/** Whether to use the "modified Akima" interpolation. */
private final boolean useModified;
/**
* Uses the original Akima algorithm.
*/
public AkimaSplineInterpolator() {
this(false);
}
/**
* @param useModified Whether to use the
* <a href="https://nl.mathworks.com/help/matlab/ref/makima.html">modified Akima</a>
* interpolation.
*/
public AkimaSplineInterpolator(boolean useModified) {
this.useModified = useModified;
}
/**
* Computes an interpolating function for the data set.
*
* @param xvals the arguments for the interpolation points
* @param yvals the values for the interpolation points
* @return a function which interpolates the data set
* @throws DimensionMismatchException if {@code xvals} and {@code yvals} have
* different sizes.
* @throws NonMonotonicSequenceException if {@code xvals} is not sorted in
* strict increasing order.
* @throws NumberIsTooSmallException if the size of {@code xvals} is smaller
* than 5.
*/
@Override
public PolynomialSplineFunction interpolate(double[] xvals,
double[] yvals)
throws DimensionMismatchException,
NumberIsTooSmallException,
NonMonotonicSequenceException {
if (xvals == null ||
yvals == null) {
throw new NullArgumentException();
}
if (xvals.length != yvals.length) {
throw new DimensionMismatchException(xvals.length, yvals.length);
}
if (xvals.length < MINIMUM_NUMBER_POINTS) {
throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
xvals.length,
MINIMUM_NUMBER_POINTS, true);
}
MathArrays.checkOrder(xvals);
final int numberOfDiffAndWeightElements = xvals.length - 1;
final double[] differences = new double[numberOfDiffAndWeightElements];
final double[] weights = new double[numberOfDiffAndWeightElements];
for (int i = 0; i < differences.length; i++) {
differences[i] = (yvals[i + 1] - yvals[i]) / (xvals[i + 1] - xvals[i]);
}
if (useModified) {
for (int i = 1; i < weights.length; i++) {
final double a = differences[i];
final double b = differences[i - 1];
weights[i] = AccurateMath.abs(a - b) + 0.5 * AccurateMath.abs(a + b);
}
} else {
for (int i = 1; i < weights.length; i++) {
weights[i] = AccurateMath.abs(differences[i] - differences[i - 1]);
}
}
// Prepare Hermite interpolation scheme.
final double[] firstDerivatives = new double[xvals.length];
for (int i = 2; i < firstDerivatives.length - 2; i++) {
final double wP = weights[i + 1];
final double wM = weights[i - 1];
if (Precision.equals(wP, 0.0) &&
Precision.equals(wM, 0.0)) {
final double xv = xvals[i];
final double xvP = xvals[i + 1];
final double xvM = xvals[i - 1];
firstDerivatives[i] = (((xvP - xv) * differences[i - 1]) + ((xv - xvM) * differences[i])) / (xvP - xvM);
} else {
firstDerivatives[i] = ((wP * differences[i - 1]) + (wM * differences[i])) / (wP + wM);
}
}
firstDerivatives[0] = differentiateThreePoint(xvals, yvals, 0, 0, 1, 2);
firstDerivatives[1] = differentiateThreePoint(xvals, yvals, 1, 0, 1, 2);
firstDerivatives[xvals.length - 2] = differentiateThreePoint(xvals, yvals, xvals.length - 2,
xvals.length - 3, xvals.length - 2,
xvals.length - 1);
firstDerivatives[xvals.length - 1] = differentiateThreePoint(xvals, yvals, xvals.length - 1,
xvals.length - 3, xvals.length - 2,
xvals.length - 1);
return interpolateHermiteSorted(xvals, yvals, firstDerivatives);
}
/**
* Three point differentiation helper, modeled off of the same method in the
* Math.NET CubicSpline class. This is used by both the Apache Math and the
* Math.NET Akima Cubic Spline algorithms
*
* @param xvals x values to calculate the numerical derivative with
* @param yvals y values to calculate the numerical derivative with
* @param indexOfDifferentiation index of the elemnt we are calculating the derivative around
* @param indexOfFirstSample index of the first element to sample for the three point method
* @param indexOfSecondsample index of the second element to sample for the three point method
* @param indexOfThirdSample index of the third element to sample for the three point method
* @return the derivative
*/
private double differentiateThreePoint(double[] xvals, double[] yvals,
int indexOfDifferentiation,
int indexOfFirstSample,
int indexOfSecondsample,
int indexOfThirdSample) {
final double x0 = yvals[indexOfFirstSample];
final double x1 = yvals[indexOfSecondsample];
final double x2 = yvals[indexOfThirdSample];
final double t = xvals[indexOfDifferentiation] - xvals[indexOfFirstSample];
final double t1 = xvals[indexOfSecondsample] - xvals[indexOfFirstSample];
final double t2 = xvals[indexOfThirdSample] - xvals[indexOfFirstSample];
final double a = (x2 - x0 - (t2 / t1 * (x1 - x0))) / (t2 * t2 - t1 * t2);
final double b = (x1 - x0 - a * t1 * t1) / t1;
return (2 * a * t) + b;
}
/**
* Creates a Hermite cubic spline interpolation from the set of (x,y) value
* pairs and their derivatives. This is modeled off of the
* InterpolateHermiteSorted method in the Math.NET CubicSpline class.
*
* @param xvals x values for interpolation
* @param yvals y values for interpolation
* @param firstDerivatives first derivative values of the function
* @return polynomial that fits the function
*/
private PolynomialSplineFunction interpolateHermiteSorted(double[] xvals,
double[] yvals,
double[] firstDerivatives) {
if (xvals.length != yvals.length) {
throw new DimensionMismatchException(xvals.length, yvals.length);
}
if (xvals.length != firstDerivatives.length) {
throw new DimensionMismatchException(xvals.length,
firstDerivatives.length);
}
final int minimumLength = 2;
if (xvals.length < minimumLength) {
throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
xvals.length, minimumLength,
true);
}
final int size = xvals.length - 1;
final PolynomialFunction[] polynomials = new PolynomialFunction[size];
final double[] coefficients = new double[4];
for (int i = 0; i < polynomials.length; i++) {
final double w = xvals[i + 1] - xvals[i];
final double w2 = w * w;
final double yv = yvals[i];
final double yvP = yvals[i + 1];
final double fd = firstDerivatives[i];
final double fdP = firstDerivatives[i + 1];
coefficients[0] = yv;
coefficients[1] = firstDerivatives[i];
coefficients[2] = (3 * (yvP - yv) / w - 2 * fd - fdP) / w;
coefficients[3] = (2 * (yv - yvP) / w + fd + fdP) / w2;
polynomials[i] = new PolynomialFunction(coefficients);
}
return new PolynomialSplineFunction(xvals, polynomials);
}
}