/**
* 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;
/**
* Time square interpolator.
* <p>
* The interpolation is linear on x y^2. The interpolator is used for interpolation on integrated variance for options.
* All values of y must be positive.
*/
final class TimeSquareCurveInterpolator implements CurveInterpolator, Serializable {
/**
* The interpolator name.
*/
public static final String NAME = "TimeSquare";
/**
* The interpolator instance.
*/
public static final CurveInterpolator INSTANCE = new TimeSquareCurveInterpolator();
/**
* 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 TimeSquareCurveInterpolator() {
}
// 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) {
ArgChecker.isTrue(xValue > 0, "Value should be stricly positive");
// 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];
if (lowerIndex == dataSize - 1) {
return y1;
}
int higherIndex = lowerIndex + 1;
double x2 = xValues[higherIndex];
double y2 = yValues[higherIndex];
double w = (x2 - xValue) / (x2 - x1);
double xy21 = x1 * y1 * y1;
double xy22 = x2 * y2 * y2;
double xy2 = w * xy21 + (1 - w) * xy22;
return Math.sqrt(xy2 / xValue);
}
@Override
protected double doFirstDerivative(double xValue) {
ArgChecker.isTrue(xValue > 0, "Value should be stricly positive");
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];
int higherIndex = index + 1;
double x2 = xValues[higherIndex];
double y2 = yValues[higherIndex];
if ((y1 < EPS) || (y2 < EPS)) {
throw new UnsupportedOperationException("node sensitivity not implemented when one node is 0 value");
}
double w = (x2 - xValue) / (x2 - x1);
double xy21 = x1 * y1 * y1;
double xy22 = x2 * y2 * y2;
double xy2 = w * xy21 + (1 - w) * xy22;
return 0.5 * (-Math.sqrt(xy2 / xValue) + (-xy21 + xy22) / (x2 - x1) / Math.sqrt(xy2 / xValue)) / xValue;
}
@Override
protected DoubleArray doParameterSensitivity(double xValue) {
double[] resultSensitivity = 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) {
resultSensitivity[dataSize - 1] = 1.0;
return DoubleArray.ofUnsafe(resultSensitivity);
}
int higherIndex = lowerIndex + 1;
double x2 = xValues[higherIndex];
double y2 = yValues[higherIndex];
if ((y1 < EPS) || (y2 < EPS)) {
throw new UnsupportedOperationException("node sensitivity not implemented when one node is 0 value");
}
double w = (x2 - xValue) / (x2 - x1);
double xy21 = x1 * y1 * y1;
double xy22 = x2 * y2 * y2;
double xy2 = w * xy21 + (1 - w) * xy22;
double resultValue = Math.sqrt(xy2 / xValue);
double resultValueBar = 1.0;
double xy2Bar = 0.5 / resultValue / xValue * resultValueBar;
double xy21Bar = w * xy2Bar;
double xy22Bar = (1 - w) * xy2Bar;
double y2Bar = 2 * x2 * y2 * xy22Bar;
double y1Bar = 2 * x1 * y1 * xy21Bar;
resultSensitivity[lowerIndex] = y1Bar;
resultSensitivity[lowerIndex + 1] = y2Bar;
return DoubleArray.ofUnsafe(resultSensitivity);
}
@Override
public BoundCurveInterpolator bind(
BoundCurveExtrapolator extrapolatorLeft,
BoundCurveExtrapolator extrapolatorRight) {
return new Bound(this, extrapolatorLeft, extrapolatorRight);
}
}
}