AkimaSplineInterpolator.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 org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
  19. import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialSplineFunction;
  20. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  21. import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
  22. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  23. import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
  24. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  25. import org.apache.commons.math4.core.jdkmath.JdkMath;
  26. import org.apache.commons.math4.legacy.core.MathArrays;
  27. import org.apache.commons.numbers.core.Precision;

  28. /**
  29.  * Computes a cubic spline interpolation for the data set using the Akima
  30.  * algorithm, as originally formulated by Hiroshi Akima in his 1970 paper
  31.  * "A New Method of Interpolation and Smooth Curve Fitting Based on Local Procedures."
  32.  * J. ACM 17, 4 (October 1970), 589-602. DOI=10.1145/321607.321609
  33.  * http://doi.acm.org/10.1145/321607.321609
  34.  * <p>
  35.  * This implementation is based on the Akima implementation in the CubicSpline
  36.  * class in the Math.NET Numerics library. The method referenced is
  37.  * "CubicSpline.InterpolateAkimaSorted".
  38.  * </p>
  39.  * <p>
  40.  * When the {@link #AkimaSplineInterpolator(boolean) argument to the constructor}
  41.  * is {@code true}, the weights are modified in order to
  42.  * <a href="https://nl.mathworks.com/help/matlab/ref/makima.html">smooth out
  43.  * spurious oscillations</a>.
  44.  * </p>
  45.  * <p>
  46.  * The {@link #interpolate(double[], double[]) interpolate} method returns a
  47.  * {@link PolynomialSplineFunction} consisting of n cubic polynomials, defined
  48.  * over the subintervals determined by the x values, {@code x[0] < x[i] ... < x[n]}.
  49.  * The Akima algorithm requires that {@code n >= 5}.
  50.  * </p>
  51.  */
  52. public class AkimaSplineInterpolator
  53.     implements UnivariateInterpolator {
  54.     /** The minimum number of points that are needed to compute the function. */
  55.     private static final int MINIMUM_NUMBER_POINTS = 5;
  56.     /** Whether to use the "modified Akima" interpolation. */
  57.     private final boolean useModified;

  58.     /**
  59.      * Uses the original Akima algorithm.
  60.      */
  61.     public AkimaSplineInterpolator() {
  62.         this(false);
  63.     }

  64.     /**
  65.      * @param useModified Whether to use the
  66.      * <a href="https://nl.mathworks.com/help/matlab/ref/makima.html">modified Akima</a>
  67.      * interpolation.
  68.      */
  69.     public AkimaSplineInterpolator(boolean useModified) {
  70.         this.useModified = useModified;
  71.     }

  72.     /**
  73.      * Computes an interpolating function for the data set.
  74.      *
  75.      * @param xvals the arguments for the interpolation points
  76.      * @param yvals the values for the interpolation points
  77.      * @return a function which interpolates the data set
  78.      * @throws DimensionMismatchException if {@code xvals} and {@code yvals} have
  79.      *         different sizes.
  80.      * @throws NonMonotonicSequenceException if {@code xvals} is not sorted in
  81.      *         strict increasing order.
  82.      * @throws NumberIsTooSmallException if the size of {@code xvals} is smaller
  83.      *         than 5.
  84.      */
  85.     @Override
  86.     public PolynomialSplineFunction interpolate(double[] xvals,
  87.                                                 double[] yvals)
  88.         throws DimensionMismatchException,
  89.                NumberIsTooSmallException,
  90.                NonMonotonicSequenceException {
  91.         if (xvals == null ||
  92.             yvals == null) {
  93.             throw new NullArgumentException();
  94.         }

  95.         if (xvals.length != yvals.length) {
  96.             throw new DimensionMismatchException(xvals.length, yvals.length);
  97.         }

  98.         if (xvals.length < MINIMUM_NUMBER_POINTS) {
  99.             throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
  100.                                                 xvals.length,
  101.                                                 MINIMUM_NUMBER_POINTS, true);
  102.         }

  103.         MathArrays.checkOrder(xvals);

  104.         final int numberOfDiffAndWeightElements = xvals.length - 1;

  105.         final double[] differences = new double[numberOfDiffAndWeightElements];
  106.         final double[] weights = new double[numberOfDiffAndWeightElements];

  107.         for (int i = 0; i < differences.length; i++) {
  108.             differences[i] = (yvals[i + 1] - yvals[i]) / (xvals[i + 1] - xvals[i]);
  109.         }

  110.         if (useModified) {
  111.             for (int i = 1; i < weights.length; i++) {
  112.                 final double a = differences[i];
  113.                 final double b = differences[i - 1];
  114.                 weights[i] = JdkMath.abs(a - b) + 0.5 * JdkMath.abs(a + b);
  115.             }
  116.         } else {
  117.             for (int i = 1; i < weights.length; i++) {
  118.                 weights[i] = JdkMath.abs(differences[i] - differences[i - 1]);
  119.             }
  120.         }

  121.         // Prepare Hermite interpolation scheme.
  122.         final double[] firstDerivatives = new double[xvals.length];

  123.         for (int i = 2; i < firstDerivatives.length - 2; i++) {
  124.             final double wP = weights[i + 1];
  125.             final double wM = weights[i - 1];
  126.             if (Precision.equals(wP, 0.0) &&
  127.                 Precision.equals(wM, 0.0)) {
  128.                 final double xv = xvals[i];
  129.                 final double xvP = xvals[i + 1];
  130.                 final double xvM = xvals[i - 1];
  131.                 firstDerivatives[i] = (((xvP - xv) * differences[i - 1]) + ((xv - xvM) * differences[i])) / (xvP - xvM);
  132.             } else {
  133.                 firstDerivatives[i] = ((wP * differences[i - 1]) + (wM * differences[i])) / (wP + wM);
  134.             }
  135.         }

  136.         firstDerivatives[0] = differentiateThreePoint(xvals, yvals, 0, 0, 1, 2);
  137.         firstDerivatives[1] = differentiateThreePoint(xvals, yvals, 1, 0, 1, 2);
  138.         firstDerivatives[xvals.length - 2] = differentiateThreePoint(xvals, yvals, xvals.length - 2,
  139.                                                                      xvals.length - 3, xvals.length - 2,
  140.                                                                      xvals.length - 1);
  141.         firstDerivatives[xvals.length - 1] = differentiateThreePoint(xvals, yvals, xvals.length - 1,
  142.                                                                      xvals.length - 3, xvals.length - 2,
  143.                                                                      xvals.length - 1);

  144.         return interpolateHermiteSorted(xvals, yvals, firstDerivatives);
  145.     }

  146.     /**
  147.      * Three point differentiation helper, modeled off of the same method in the
  148.      * Math.NET CubicSpline class. This is used by both the Apache Math and the
  149.      * Math.NET Akima Cubic Spline algorithms
  150.      *
  151.      * @param xvals x values to calculate the numerical derivative with
  152.      * @param yvals y values to calculate the numerical derivative with
  153.      * @param indexOfDifferentiation index of the elemnt we are calculating the derivative around
  154.      * @param indexOfFirstSample index of the first element to sample for the three point method
  155.      * @param indexOfSecondsample index of the second element to sample for the three point method
  156.      * @param indexOfThirdSample index of the third element to sample for the three point method
  157.      * @return the derivative
  158.      */
  159.     private double differentiateThreePoint(double[] xvals, double[] yvals,
  160.                                            int indexOfDifferentiation,
  161.                                            int indexOfFirstSample,
  162.                                            int indexOfSecondsample,
  163.                                            int indexOfThirdSample) {
  164.         final double x0 = yvals[indexOfFirstSample];
  165.         final double x1 = yvals[indexOfSecondsample];
  166.         final double x2 = yvals[indexOfThirdSample];

  167.         final double t = xvals[indexOfDifferentiation] - xvals[indexOfFirstSample];
  168.         final double t1 = xvals[indexOfSecondsample] - xvals[indexOfFirstSample];
  169.         final double t2 = xvals[indexOfThirdSample] - xvals[indexOfFirstSample];

  170.         final double a = (x2 - x0 - (t2 / t1 * (x1 - x0))) / (t2 * t2 - t1 * t2);
  171.         final double b = (x1 - x0 - a * t1 * t1) / t1;

  172.         return (2 * a * t) + b;
  173.     }

  174.     /**
  175.      * Creates a Hermite cubic spline interpolation from the set of (x,y) value
  176.      * pairs and their derivatives. This is modeled off of the
  177.      * InterpolateHermiteSorted method in the Math.NET CubicSpline class.
  178.      *
  179.      * @param xvals x values for interpolation
  180.      * @param yvals y values for interpolation
  181.      * @param firstDerivatives first derivative values of the function
  182.      * @return polynomial that fits the function
  183.      */
  184.     private PolynomialSplineFunction interpolateHermiteSorted(double[] xvals,
  185.                                                               double[] yvals,
  186.                                                               double[] firstDerivatives) {
  187.         if (xvals.length != yvals.length) {
  188.             throw new DimensionMismatchException(xvals.length, yvals.length);
  189.         }

  190.         if (xvals.length != firstDerivatives.length) {
  191.             throw new DimensionMismatchException(xvals.length,
  192.                                                  firstDerivatives.length);
  193.         }

  194.         final int minimumLength = 2;
  195.         if (xvals.length < minimumLength) {
  196.             throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
  197.                                                 xvals.length, minimumLength,
  198.                                                 true);
  199.         }

  200.         final int size = xvals.length - 1;
  201.         final PolynomialFunction[] polynomials = new PolynomialFunction[size];
  202.         final double[] coefficients = new double[4];

  203.         for (int i = 0; i < polynomials.length; i++) {
  204.             final double w = xvals[i + 1] - xvals[i];
  205.             final double w2 = w * w;

  206.             final double yv = yvals[i];
  207.             final double yvP = yvals[i + 1];

  208.             final double fd = firstDerivatives[i];
  209.             final double fdP = firstDerivatives[i + 1];

  210.             coefficients[0] = yv;
  211.             coefficients[1] = firstDerivatives[i];
  212.             coefficients[2] = (3 * (yvP - yv) / w - 2 * fd - fdP) / w;
  213.             coefficients[3] = (2 * (yv - yvP) / w + fd + fdP) / w2;
  214.             polynomials[i] = new PolynomialFunction(coefficients);
  215.         }

  216.         return new PolynomialSplineFunction(xvals, polynomials);
  217.     }
  218. }