/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.market.curve.interpolator;
import java.io.Serializable;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
/**
* Extrapolator implementation that returns a value linearly in terms of {@code (x[i], x[i] * y[i])}.
* <p>
* The gradient of the extrapolation is obtained from the gradient of the interpolated curve on {@code (x[i], x[i] * y[i])}
* at the first/last node.
* <p>
* The extrapolation is ambiguous at x=0. Thus the following rule applies:
* The x value of the first node must be strictly negative for the left extrapolation, whereas the x value of
* the last node must be strictly positive for the right extrapolation.
*/
final class ProductLinearCurveExtrapolator
implements CurveExtrapolator, Serializable {
/**
* The serialization version id.
*/
private static final long serialVersionUID = 1L;
/**
* The extrapolator name.
*/
public static final String NAME = "ProductLinear";
/**
* The extrapolator instance.
*/
public static final CurveExtrapolator INSTANCE = new ProductLinearCurveExtrapolator();
/**
* The epsilon value.
*/
private static final double EPS = 1e-8;
/**
* Restricted constructor.
*/
private ProductLinearCurveExtrapolator() {
}
// resolve instance
private Object readResolve() {
return INSTANCE;
}
//-------------------------------------------------------------------------
@Override
public String getName() {
return NAME;
}
@Override
public BoundCurveExtrapolator bind(DoubleArray xValues, DoubleArray yValues, BoundCurveInterpolator interpolator) {
return new Bound(xValues, yValues, interpolator);
}
//-------------------------------------------------------------------------
@Override
public String toString() {
return NAME;
}
//-------------------------------------------------------------------------
/**
* Bound extrapolator.
*/
static class Bound implements BoundCurveExtrapolator {
private final int nodeCount;
private final double lastXValue;
private final double lastYValue;
private final double eps;
private final double lastGradient;
private final DoubleArray lastSens;
private final DoubleArray lastGradSens;
private final double firstXValue;
private final double firstYValue;
private final double firstGradient;
private final DoubleArray firstSens;
private final DoubleArray firstGradSens;
Bound(DoubleArray xValues, DoubleArray yValues, BoundCurveInterpolator interpolator) {
this.nodeCount = xValues.size();
this.firstXValue = xValues.get(0);
this.firstYValue = yValues.get(0);
this.lastXValue = xValues.get(nodeCount - 1);
this.lastYValue = yValues.get(nodeCount - 1);
this.eps = EPS * (lastXValue - firstXValue);
// left
this.firstGradient = interpolator.firstDerivative(firstXValue);
this.firstSens = interpolator.parameterSensitivity(firstXValue);
this.firstGradSens = interpolator.parameterSensitivity(firstXValue + eps).minus(firstSens).dividedBy(eps);
// right
this.lastGradient = interpolator.firstDerivative(lastXValue);
this.lastSens = interpolator.parameterSensitivity(lastXValue);
this.lastGradSens = lastSens.minus(interpolator.parameterSensitivity(lastXValue - eps)).dividedBy(eps);
}
//-------------------------------------------------------------------------
@Override
public double leftExtrapolate(double xValue) {
ArgChecker.isTrue(firstXValue < -EPS, "the first x value must be negative for left extrapolation");
return firstGradient * firstXValue * (1d - firstXValue / xValue) + firstYValue;
}
@Override
public double leftExtrapolateFirstDerivative(double xValue) {
ArgChecker.isTrue(firstXValue < -EPS, "the first x value must be negative for left extrapolation");
return firstGradient * Math.pow(firstXValue / xValue, 2);
}
@Override
public DoubleArray leftExtrapolateParameterSensitivity(double xValue) {
ArgChecker.isTrue(firstXValue < -EPS, "the first x value must be negative for left extrapolation");
double factor = (1d - firstXValue / xValue) * firstXValue;
return firstGradSens.multipliedBy(factor).plus(firstSens);
}
//-------------------------------------------------------------------------
@Override
public double rightExtrapolate(double xValue) {
ArgChecker.isTrue(lastXValue > EPS, "the last x value must be positive for right extrapolation");
return lastGradient * lastXValue * (1d - lastXValue / xValue) + lastYValue;
}
@Override
public double rightExtrapolateFirstDerivative(double xValue) {
ArgChecker.isTrue(lastXValue > EPS, "the last x value must be positive for right extrapolation");
return lastGradient * Math.pow(lastXValue / xValue, 2);
}
@Override
public DoubleArray rightExtrapolateParameterSensitivity(double xValue) {
ArgChecker.isTrue(lastXValue > EPS, "the last x value must be positive for right extrapolation");
double factor = (1d - lastXValue / xValue) * lastXValue;
return lastGradSens.multipliedBy(factor).plus(lastSens);
}
}
}