Jonathan Fortin
Jonathan Fortin

Reputation: 342

Extrapolation in java

I've been able to use Apache Math's interpolation using the LinearInterpolator().interpolate(x1, y1). Unfortunately, I could not find a way to extrapolate.

How can I do linear extrapolation in java?

x1 = [1, 2, 3, 4, 5];

y1 = [2, 4, 8, 16, 32];

I would like to know the values of any x2 not just the one in the range of the x1.

If I try to extract the value of 6 I get an: OutOfRangeException if {@code v} is outside of the domain of the * spline function (smaller than the smallest knot point or larger than the largest knot point).

Edit: Here is my simple interpolate function. I would like an option to enable the extrapolation just like in MathLab(interp2). Using x1 and y1 arrays an input for that function I get the Apache's OutOfRangeException because the value 6 is not contained in the x1 array.

public static List<Double> interpolateLinear(double[] x1, double[] y1, Double[] x2) {
    List<Double> resultList;
    final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
    resultList = Arrays.stream(x2).map(aDouble -> function.value(aDouble)).collect(Collectors.toList());
    return resultList;
}

Edit2: Had to read a little bit on the .value method of the PolynomialSplineFunction object to get it right but there it goes (all the credit goes to user Joni) Thanks man:

public static double[] interpolateLinear(double[] x1, double[] y1, double[] x2) {
    final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
    final PolynomialFunction[] splines = function.getPolynomials();
    final PolynomialFunction firstFunction = splines[0];
    final PolynomialFunction lastFunction = splines[splines.length - 1];

    final double[] knots = function.getKnots();
    final double firstKnot = knots[0];
    final double lastKnot = knots[knots.length - 1];

    double[] resultList = Arrays.stream(x2).map(aDouble -> {
        if (aDouble > lastKnot) {
            return lastFunction.value(aDouble - knots[knots.length - 2]);
        } else if (aDouble < firstKnot)
            return firstFunction.value(aDouble - knots[0]);
        return function.value(aDouble);
    }).toArray();
    return resultList;
}

Upvotes: 7

Views: 5867

Answers (3)

Marcos Roberto Silva
Marcos Roberto Silva

Reputation: 104

Just sharing a complete example based on the answer provided by Joni:

import java.util.Arrays;

import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;

public class App {
    public static void main(String[] args) {

        double[] x1 = { 1, 2, 3, 4, 5 };
        double[] y1 = { 2, 4, 8, 16, 32 };
        double[] x2 = { 6, 7 };

        double[] res = interpolateLinear(x1, y1, x2);

        for (int i = 0; i < res.length; i++) {
            System.out.println("Value: " + x2[i] + " => extrapolation: " + res[i]);
        }

    }

    public static double[] interpolateLinear(double[] x1, double[] y1, double[] x2) {
        final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
        final PolynomialFunction[] splines = function.getPolynomials();
        final PolynomialFunction firstFunction = splines[0];
        final PolynomialFunction lastFunction = splines[splines.length - 1];

        final double[] knots = function.getKnots();
        final double firstKnot = knots[0];
        final double lastKnot = knots[knots.length - 1];

        double[] resultList = Arrays.stream(x2).map(aDouble -> {
            if (aDouble > lastKnot) {
                return lastFunction.value(aDouble - knots[knots.length - 2]);
            } else if (aDouble < firstKnot)
                return firstFunction.value(aDouble - knots[0]);
            return function.value(aDouble);
        }).toArray();
        return resultList;
    }
}

Upvotes: 0

XYZ
XYZ

Reputation: 382

I have similar problem, the interpolation part is a cubic spline function, and math3.analysis.polynomials.PolynomialSplineFunction does not support the extrapolation.

In the end, I decide to write the linear extrapolation based on the leftest(/rightest) two points (i.e. x1,x2 and y1, y2). I need the extrapolation part to avoid that the function fails or get any very irregular values in the extrapolation region. In my example, I hard coded such that the extrapolated value should stay in [0.5* y1, 2 * y1] (left side) or [0.5 * yn, 2 *yn] (right side).

As mentioned by Joni, the extrapolation is dangerous, and it could leads to unexpected results. Be careful. The linear extrapolation can be replaced by any other kind extrapolation, depending on how you write the code (e.g. using the derivative at the right/left point and inferring a quadratic function for extrapolation.)

    public static double getValue(PolynomialSplineFunction InterpolationFunction, double v) {
    try {
        return InterpolationFunction.value(v);
    } catch (OutOfRangeException e) {
        // add the extrapolation function: we use linear extrapolation based on the slope of the two points on the left or right

        double[] InterpolationKnots = InterpolationFunction.getKnots();

        int n = InterpolationKnots.length;

        double first, second, firstValue, secondValue;

        if (v < InterpolationKnots[0])
        {   // extrapolation from the left side, linear extrapolation based on the first two points on the left
            first = InterpolationKnots[0];   // the leftest point
            second = InterpolationKnots[1];  // the second leftest point
        }
        else {     // extrapolation on the right side, linear extrapolation based on the first two points on the right
            first = InterpolationKnots[n - 1];  // the rightest point
            second = InterpolationKnots[n - 2];  // the second rightest point
        }

        firstValue = InterpolationFunction.value(first);
        secondValue = InterpolationFunction.value(second);
        double extrapolatedValue = (firstValue - secondValue) / (first - second) * (v - first) + firstValue;


        // add a boundary to the extrapolated value so that it is within [0.5, 2] * firstValue
        if (extrapolatedValue > 2 * firstValue){ extrapolatedValue = 2 * firstValue;}
        if (extrapolatedValue < 0.5 * firstValue) {extrapolatedValue = 0.5* firstValue;}

        return extrapolatedValue;
    }
}

Upvotes: 0

Joni
Joni

Reputation: 111369

You can get the first and last polynomial splines from the interpolator, and use those to extrapolate.

PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
PolynomialFunction[] splines = function.getPolynomials();
PolynomialFunction first = splines[0];
PolynomialFunction last = splines[splines.length-1];
// use first and last to extrapolate 

You won't get 64 from 6 though. You should expect 48 from a linear extrapolation. Which goes to show that extrapolation is bound to give you wrong answers.

Upvotes: 11

Related Questions