/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.math.interpolation;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle;
/**
* Log-linear extrapolator: the extrapolant is exp(f(x)) where f(x) is a linear function
* which is smoothly connected with a log-interpolator exp(F(x)), such as {@link LogNaturalCubicMonotonicityPreservingInterpolator1D},
* i.e., F'(x) = f'(x) at a respectivie endpoint.
*/
public class LogLinearExtrapolator1D extends Interpolator1D {
private static final long serialVersionUID = 1L;
private final Interpolator1D _interpolator;
private final double _eps;
/**
* @param interpolator Interpolator for specifying the first derivative value at an endpoint
*/
public LogLinearExtrapolator1D(final Interpolator1D interpolator) {
this(interpolator, 1e-8);
}
/**
* @param interpolator Interpolator for specifying the first derivative value at an endpoint
* @param eps Bump parameter of finite difference approximation for the first derivative value
*/
public LogLinearExtrapolator1D(final Interpolator1D interpolator, double eps) {
Validate.notNull(interpolator, "interpolator");
_interpolator = interpolator;
_eps = eps;
}
@Override
public Interpolator1DDataBundle getDataBundle(final double[] x, final double[] y) {
return _interpolator.getDataBundle(x, y);
}
@Override
public Interpolator1DDataBundle getDataBundleFromSortedArrays(final double[] x, final double[] y) {
return _interpolator.getDataBundleFromSortedArrays(x, y);
}
@Override
public Double interpolate(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
if (value < data.firstKey()) {
return leftExtrapolate(data, value);
} else if (value > data.lastKey()) {
return rightExtrapolate(data, value);
}
throw new IllegalArgumentException("Value " + value + " was within data range");
}
@Override
public double firstDerivative(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
if (value < data.firstKey()) {
return leftExtrapolateDerivative(data, value);
} else if (value > data.lastKey()) {
return rightExtrapolateDerivative(data, value);
}
throw new IllegalArgumentException("Value " + value + " was within data range");
}
@Override
public double[] getNodeSensitivitiesForValue(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
if (value < data.firstKey()) {
return getLeftSensitivities(data, value);
} else if (value > data.lastKey()) {
return getRightSensitivities(data, value);
}
throw new IllegalArgumentException("Value " + value + " was within data range");
}
private Double leftExtrapolate(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
final double x = data.firstKey();
final double y = Math.log(data.firstValue());
final double m = _interpolator.firstDerivative(data, x) / _interpolator.interpolate(data, x);
return Math.exp(y + (value - x) * m);
}
private Double rightExtrapolate(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
final double x = data.lastKey();
final double y = Math.log(data.lastValue());
final double m = _interpolator.firstDerivative(data, x) / _interpolator.interpolate(data, x);
return Math.exp(y + (value - x) * m);
}
private Double leftExtrapolateDerivative(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
final double x = data.firstKey();
final double y = Math.log(data.firstValue());
final double m = _interpolator.firstDerivative(data, x) / _interpolator.interpolate(data, x);
return m * Math.exp(y + (value - x) * m);
}
private Double rightExtrapolateDerivative(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
Validate.notNull(value, "value");
final double x = data.lastKey();
final double y = Math.log(data.lastValue());
final double m = _interpolator.firstDerivative(data, x) / _interpolator.interpolate(data, x);
return m * Math.exp(y + (value - x) * m);
}
private double[] getLeftSensitivities(final Interpolator1DDataBundle data, final Double value) {
final double eps = _eps * (data.lastKey() - data.firstKey());
final double x = data.firstKey();
final double resValueInterpolator = _interpolator.interpolate(data, x + eps);
final double resValueExtrapolator = leftExtrapolate(data, value);
final double[] result = _interpolator.getNodeSensitivitiesForValue(data, x + eps);
final double factor1 = (value - x) / eps;
final double factor2 = factor1 * resValueExtrapolator / resValueInterpolator;
final int n = result.length;
for (int i = 1; i < n; i++) {
result[i] *= factor2;
}
result[0] = result[0] * factor2 + (1. - factor1) * resValueExtrapolator / data.firstValue();
return result;
}
private double[] getRightSensitivities(final Interpolator1DDataBundle data, final Double value) {
final double eps = _eps * (data.lastKey() - data.firstKey());
final double x = data.lastKey();
final double resValueInterpolator = _interpolator.interpolate(data, x - eps);
final double resValueExtrapolator = rightExtrapolate(data, value);
final double[] result = _interpolator.getNodeSensitivitiesForValue(data, x - eps);
final double factor1 = (value - x) / eps;
final double factor2 = factor1 * resValueExtrapolator / resValueInterpolator;
final int n = result.length;
for (int i = 0; i < n - 1; i++) {
result[i] *= -factor2;
}
result[n - 1] = (1. + factor1) * resValueExtrapolator / data.lastValue() - result[n - 1] * factor2;
return result;
}
}