/**
* 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.array.DoubleArray;
/**
* The interpolation is linear on y^2. The interpolator is used for interpolation on variance for options.
* All values of y must be positive.
*/
final class SquareLinearCurveInterpolator implements CurveInterpolator, Serializable {
/**
* The interpolator name.
*/
public static final String NAME = "SquareLinear";
/**
* The interpolator instance.
*/
public static final CurveInterpolator INSTANCE = new SquareLinearCurveInterpolator();
/**
* The serialization version id.
*/
private static final long serialVersionUID = 1L;
/**
* Level below which the value is consider to be 0.
*/
private static final double EPS = 1.0E-10;
/**
* Restricted constructor.
*/
private SquareLinearCurveInterpolator() {
}
// resolve instance
private Object readResolve() {
return INSTANCE;
}
//-------------------------------------------------------------------------
@Override
public String getName() {
return NAME;
}
@Override
public BoundCurveInterpolator bind(DoubleArray xValues, DoubleArray yValues) {
return new Bound(xValues, yValues);
}
//-----------------------------------------------------------------------
@Override
public String toString() {
return NAME;
}
//-------------------------------------------------------------------------
/**
* Bound interpolator.
*/
static class Bound extends AbstractBoundCurveInterpolator {
private final double[] xValues;
private final double[] yValues;
private final int dataSize;
Bound(DoubleArray xValues, DoubleArray yValues) {
super(xValues, yValues);
this.xValues = xValues.toArrayUnsafe();
this.yValues = yValues.toArrayUnsafe();
this.dataSize = xValues.size();
}
Bound(Bound base, BoundCurveExtrapolator extrapolatorLeft, BoundCurveExtrapolator extrapolatorRight) {
super(base, extrapolatorLeft, extrapolatorRight);
this.xValues = base.xValues;
this.yValues = base.yValues;
this.dataSize = xValues.length;
}
//-------------------------------------------------------------------------
@Override
protected double doInterpolate(double xValue) {
// x-value is less than the x-value of the last node (lowerIndex < intervalCount)
int lowerIndex = lowerBoundIndex(xValue, xValues);
double x1 = xValues[lowerIndex];
double y1 = yValues[lowerIndex];
int higherIndex = lowerIndex + 1;
double x2 = xValues[higherIndex];
double y2 = yValues[higherIndex];
double w = (x2 - xValue) / (x2 - x1);
double y21 = y1 * y1;
double y22 = y2 * y2;
double ySq = w * y21 + (1.0 - w) * y22;
return Math.sqrt(ySq);
}
@Override
protected double doFirstDerivative(double xValue) {
int lowerIndex = lowerBoundIndex(xValue, xValues);
int index;
// check if x-value is at the last node
if (lowerIndex == dataSize - 1) {
index = dataSize - 2;
} else {
index = lowerIndex;
}
double x1 = xValues[index];
double y1 = yValues[index];
double x2 = xValues[index + 1];
double y2 = yValues[index + 1];
if ((y1 < EPS) && (y2 >= EPS) && (xValue - x1) < EPS) { // On one vertex with value 0, other vertex not 0
throw new IllegalArgumentException("ask for first derivative on a value without derivative; value " + xValue +
" is close to vertex " + x1 + " and value at vertex is " + y1);
}
if ((y2 < EPS) && (y1 >= EPS) && (x2 - xValue) < EPS) { // On one vertex with value 0, other vertex not 0
throw new IllegalArgumentException("ask for first derivative on a value without derivative; value " + xValue +
" is close to vertex " + x2 + " and value at vertex is " + y2);
}
if ((y1 < EPS) && (y2 < EPS)) { // Both vertices have 0 value, return 0.
return 0.0;
}
double w = (x2 - xValue) / (x2 - x1);
double y21 = y1 * y1;
double y22 = y2 * y2;
double ySq = w * y21 + (1.0 - w) * y22;
return 0.5 * (y22 - y21) / (x2 - x1) / Math.sqrt(ySq);
}
@Override
protected DoubleArray doParameterSensitivity(double xValue) {
double[] result = new double[dataSize];
int lowerIndex = lowerBoundIndex(xValue, xValues);
double x1 = xValues[lowerIndex];
double y1 = yValues[lowerIndex];
// check if x-value is at the last node
if (lowerIndex == dataSize - 1) {
result[dataSize - 1] = 1.0;
return DoubleArray.ofUnsafe(result);
}
int higherIndex = lowerIndex + 1;
double x2 = xValues[higherIndex];
double y2 = yValues[higherIndex];
if ((xValue - x1) < EPS) { // On or very close to Vertex 1
result[lowerIndex] = 1.0d;
return DoubleArray.ofUnsafe(result);
}
if ((x2 - xValue) < EPS) { // On or very close to Vertex 2
result[lowerIndex + 1] = 1.0d;
return DoubleArray.ofUnsafe(result);
}
double w2 = (x2 - xValue) / (x2 - x1);
if ((y2 < EPS) && (y1 < EPS)) { // Both values very close to 0
result[lowerIndex] = Math.sqrt(w2);
result[lowerIndex + 1] = Math.sqrt(1.0d - w2);
return DoubleArray.ofUnsafe(result);
}
double y21 = y1 * y1;
double y22 = y2 * y2;
double ySq = w2 * y21 + (1.0 - w2) * y22;
// Backward
double ySqBar = 0.5 / Math.sqrt(ySq);
double y22Bar = (1.0 - w2) * ySqBar;
double y21Bar = w2 * ySqBar;
double y1Bar = 2 * y1 * y21Bar;
double y2Bar = 2 * y2 * y22Bar;
result[lowerIndex] = y1Bar;
result[lowerIndex + 1] = y2Bar;
return DoubleArray.ofUnsafe(result);
}
@Override
public BoundCurveInterpolator bind(
BoundCurveExtrapolator extrapolatorLeft,
BoundCurveExtrapolator extrapolatorRight) {
return new Bound(this, extrapolatorLeft, extrapolatorRight);
}
}
}