/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.math.interpolation;
import java.io.Serializable;
import java.util.Map;
import java.util.SortedMap;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.math.differentiation.FiniteDifferenceType;
import com.opengamma.analytics.math.differentiation.ScalarFirstOrderDifferentiator;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle;
/**
* A base class for interpolation in one dimension.
*/
public abstract class Interpolator1D implements Interpolator<Interpolator1DDataBundle, Double>, Serializable {
private static final long serialVersionUID = 1L;
private static final double EPS = 1e-6;
@Override
public abstract Double interpolate(Interpolator1DDataBundle data, Double value);
/**
* Computes the gradient of the interpolant at the value.
* <p>
* Note: this is computed by finite difference - this method is expected to be overridden for concrete classes with an analytical calculation
* @param data Interpolation Data
* @param value The value for which the gradient is computed
* @return The gradient
*/
public double firstDerivative(final Interpolator1DDataBundle data, final Double value) {
double range = data.lastKey() - data.firstKey();
Function1D<Double, Boolean> domain = new Function1D<Double, Boolean>() {
@Override
public Boolean evaluate(Double x) {
return x >= data.firstKey() && x <= data.lastKey();
}
};
ScalarFirstOrderDifferentiator diff = new ScalarFirstOrderDifferentiator(FiniteDifferenceType.CENTRAL, range * EPS);
Function1D<Double, Double> func = getFunction(data);
Function1D<Double, Double> gradFunc = diff.differentiate(func, domain);
return gradFunc.evaluate(value);
}
/**
* Generate a 1D function of the interpolant from the interpolator and the data bundle
* @param data The knots and computed values used by the interpolator
* @return a 1D function
*/
public Function1D<Double, Double> getFunction(final Interpolator1DDataBundle data) {
return new Function1D<Double, Double>() {
@Override
public Double evaluate(Double x) {
return interpolate(data, x);
}
};
}
/**
* Generate a 1D function representing the gradient of the interpolant from the interpolator and the data bundle
* @param The knots and computed values used by the interpolator
* @return a 1D function of the gradient
*/
public Function1D<Double, Double> getGradientFunction(final Interpolator1DDataBundle data) {
/*
* Implementation note: It would be more efficient to have the finite difference mechanism (found in firstDerivative)
* here and have firstDerivative call this rather than the other way round. However firstDerivative is overridden in
* concrete implementations (with an analytic calculation), which would mean calls to getGradientFunction would be
* computed by FD even if firstDerivative was over ridden.
*/
return new Function1D<Double, Double>() {
@Override
public Double evaluate(Double x) {
return firstDerivative(data, x);
}
};
}
/**
* Computes the sensitivities of the interpolated value to the input data y.
* @param data The interpolation data.
* @param value The value for which the interpolation is computed.
* @param useFiniteDifferenceSensitivities Use finite difference approximation if true
* @return The sensitivity.
*/
public double[] getNodeSensitivitiesForValue(final Interpolator1DDataBundle data, final Double value, final boolean useFiniteDifferenceSensitivities) {
return useFiniteDifferenceSensitivities ? getFiniteDifferenceSensitivities(data, value) : getNodeSensitivitiesForValue(data, value);
}
/**
* Computes the sensitivities of the interpolated value to the input data y by using a methodology defined in a respective subclass
* @param data The interpolation data.
* @param value The value for which the interpolation is computed.
* @return The sensitivity.
*/
public abstract double[] getNodeSensitivitiesForValue(Interpolator1DDataBundle data, Double value);
/**
* Computes the sensitivities of the interpolated value to the input data y by using central finite difference approximation.
* @param data The interpolation data.
* @param value The value for which the interpolation is computed.
* @return The sensitivity.
*/
protected double[] getFiniteDifferenceSensitivities(final Interpolator1DDataBundle data, final Double value) {
Validate.notNull(data, "data");
final double[] x = data.getKeys();
final double[] y = data.getValues();
final int n = x.length;
final double[] result = new double[n];
final Interpolator1DDataBundle dataUp = getDataBundleFromSortedArrays(x, y);
final Interpolator1DDataBundle dataDown = getDataBundleFromSortedArrays(x, y);
for (int i = 0; i < n; i++) {
if (i != 0) {
dataUp.setYValueAtIndex(i - 1, y[i - 1]);
dataDown.setYValueAtIndex(i - 1, y[i - 1]);
}
dataUp.setYValueAtIndex(i, y[i] + EPS);
dataDown.setYValueAtIndex(i, y[i] - EPS);
final double up = interpolate(dataUp, value);
final double down = interpolate(dataDown, value);
result[i] = (up - down) / 2 / EPS;
}
return result;
}
/**
* Construct Interpolator1DDataBundle from unsorted arrays
* @param x X values of data
* @param y Y values of data
* @return Interpolator1DDataBundle
*/
public abstract Interpolator1DDataBundle getDataBundle(double[] x, double[] y);
/**
* Construct Interpolator1DDataBundle from sorted arrays, i.e, x[0] < x[1] < x[2], .....
* @param x X values of data
* @param y Y values of data
* @return Interpolator1DDataBundle
*/
public abstract Interpolator1DDataBundle getDataBundleFromSortedArrays(double[] x, double[] y);
/**
* Construct Interpolator1DDataBundle from Map
* @param data Data containing x values and y values
* @return Interpolator1DDataBundle
*/
public Interpolator1DDataBundle getDataBundle(final Map<Double, Double> data) {
Validate.notNull(data, "Backing data for interpolation must not be null.");
Validate.notEmpty(data, "Backing data for interpolation must not be empty.");
if (data instanceof SortedMap) {
final double[] keys = ArrayUtils.toPrimitive(data.keySet().toArray(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY));
final double[] values = ArrayUtils.toPrimitive(data.values().toArray(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY));
return getDataBundleFromSortedArrays(keys, values);
}
final double[] keys = new double[data.size()];
final double[] values = new double[data.size()];
int i = 0;
for (final Map.Entry<Double, Double> entry : data.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
i++;
}
return getDataBundle(keys, values);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return true;
}
/**
* @param o Reference class
* @return true if two objects are the same class
*/
protected boolean classEquals(final Object o) {
if (o == null) {
return false;
}
return getClass().equals(o.getClass());
}
}