/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.math.surface;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.interpolation.data.ArrayInterpolator1DDataBundle;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.ParallelArrayBinarySort;
import com.opengamma.util.tuple.Pair;
// NOTE kirk 2016-03-01 -- This class is *INTENTIONALLY* not following current
// best practices for generalization or code structure. It is 100% optimized for
// performance in the case of running large numbers of historical simulations
// on option pricing. DO NOT attempt to make this class more stylistically in line
// with other code without carefully understanding and measuring the performance
// impact of what you're doing.
// It is *COMPLETELY INTENTIONAL* that there is not a single collection or
// primitive wrapper in this class. Do not add either.
/**
* A fast implementation of {@code Surface} designed specifically for use in grid-based
* volatilty surfaces that commonly come up in exchange traded derivatives contexts.
* It is optimized for performance over generality, and avoids unnecessary data structure
* copies or mutations.
* It has a relatively expensive construction cost to allow fast interpolation
* and transformation.
*/
public final class FastGridInterpolatedDoublesSurface extends Surface<Double, Double, Double> {
private static final long serialVersionUID = 1L;
private final int _size;
private final double[] _xvalues;
private final Interpolator1DDataBundle[] _yzBundles;
private final Interpolator1D _xInterpolator;
private final Interpolator1D _yInterpolator;
private FastGridInterpolatedDoublesSurface(
int size,
double[] xvalues, Interpolator1DDataBundle[] yzBundles,
Interpolator1D xInterpolator, Interpolator1D yInterpolator,
String name) {
super(name);
// Intentionally we don't do ArgumentChecker here. It's done in the factory methods.
_size = size;
_xvalues = xvalues;
_yzBundles = yzBundles;
_xInterpolator = xInterpolator;
_yInterpolator = yInterpolator;
}
/**
* Factory method for constructing a new instance using primitive inputs.
* This method will not copy the inputs, but will sort them in place. Therefore
* calling code should release control over the arrays.
*
* @param xdata X values in triplet form.
* @param ydata Y values in triplet form.
* @param zdata Z values in triplet form.
* @param xInterpolator X interpolator to use
* @param yInterpolator Y interpolator to use
* @param name Curve name
* @return a new surface instance
*/
public static FastGridInterpolatedDoublesSurface fromUnsortedNoCopy(
double[] xdata, double[] ydata, double[] zdata,
Interpolator1D xInterpolator, Interpolator1D yInterpolator,
String name) {
ArgumentChecker.notNull(xdata, "xdata");
ArgumentChecker.notNull(ydata, "ydata");
ArgumentChecker.notNull(zdata, "zdata");
ArgumentChecker.notNull(xInterpolator, "xInterpolator");
ArgumentChecker.notNull(yInterpolator, "yInterpolator");
ArgumentChecker.isTrue(xdata.length > 0, "Must have non-zero size");
ArgumentChecker.isTrue(xdata.length == ydata.length, "xdata and ydata must be same size");
ArgumentChecker.isTrue(xdata.length == zdata.length, "xdata and zdata must be same size");
// NOTE -- This method would be so much easier with built-in tuples.....
// First, order xyz triplet by x
ParallelArrayBinarySort.parallelBinarySort(xdata, ydata, zdata);
// First pass through xdata: just check the cardinality of x and yz data sets.
// Start the loop intentionally at 1 for clarity as we know that there's at least
// one value and we get the first x value outside the loop.
double currentX = xdata[0];
int numXValues = 1;
for (int i = 1; i < xdata.length; i++) {
if (Double.doubleToRawLongBits(currentX) != Double.doubleToRawLongBits(xdata[i])) {
numXValues++;
currentX = xdata[i];
}
}
// Now we know the cardinality of xdata, we need to determine the cardinality
// of each yz set.
int[] numYZValues = new int[numXValues];
int yzValuesInThisLoop = 0;
int xOrdinal = 0;
currentX = xdata[0];
for (int i = 0; i < xdata.length; i++) {
if (Double.doubleToRawLongBits(currentX) != Double.doubleToRawLongBits(xdata[i])) {
numYZValues[xOrdinal] = yzValuesInThisLoop;
xOrdinal++;
yzValuesInThisLoop = 0;
currentX = xdata[i];
}
yzValuesInThisLoop++;
}
numYZValues[xOrdinal] = yzValuesInThisLoop;
// Second pass: actually build the yz arrays.
// The logic by looping on n makes the logic simpler than checking for
// changes in the xvalue and should make for better inlining and codegen.
Interpolator1DDataBundle[] bundles = new Interpolator1DDataBundle[numXValues];
double[] xvalues = new double[numXValues];
int indexIntoInputArrays = 0;
for (int i = 0; i < numXValues; i++) {
xvalues[i] = xdata[indexIntoInputArrays];
double[] ybundle = new double[numYZValues[i]];
double[] zbundle = new double[numYZValues[i]];
for (int j = 0; j < numYZValues[i]; j++) {
ybundle[j] = ydata[indexIntoInputArrays];
zbundle[j] = zdata[indexIntoInputArrays];
indexIntoInputArrays++;
}
// We know yz is unsorted at this stage. But there's no need to copy because
// we're dropping the reference at the end of this.
bundles[i] = new ArrayInterpolator1DDataBundle(ybundle, zbundle, false, false);
}
assert xvalues.length == bundles.length;
return new FastGridInterpolatedDoublesSurface(xdata.length, xvalues, bundles, xInterpolator, yInterpolator, name);
}
/**
* Duplicate this surface, but multiply every Z value by a particular factor.
*
* @param factor number to multiply every Z value by
* @return A duplicate surface with modified Z values
*/
public FastGridInterpolatedDoublesSurface withMultiplicativeZTransformation(double factor) {
Interpolator1DDataBundle[] yzBundles = new Interpolator1DDataBundle[_yzBundles.length];
for (int i = 0; i < _xvalues.length; i++) {
double[] oldZValues = _yzBundles[i].getValues();
double[] newZValues = new double[oldZValues.length];
for (int j = 0; j < oldZValues.length; j++) {
newZValues[j] = oldZValues[j] * factor;
}
// yz is still sorted from the constructor. So don't need to sort or copy.
yzBundles[i] = new ArrayInterpolator1DDataBundle(_yzBundles[i].getKeys(), newZValues, true, false);
}
return new FastGridInterpolatedDoublesSurface(_size, _xvalues, yzBundles, _xInterpolator, _yInterpolator, getName());
}
@Override
public Double[] getXData() {
throw new UnsupportedOperationException("This method should never have been added to Surface<?,?,?>");
}
@Override
public Double[] getYData() {
throw new UnsupportedOperationException("This method should never have been added to Surface<?,?,?>");
}
@Override
public Double[] getZData() {
throw new UnsupportedOperationException("This method should never have been added to Surface<?,?,?>");
}
@Override
public int size() {
return _size;
}
@Override
public Double getZValue(Double x, Double y) {
ArgumentChecker.notNull(x, "x");
ArgumentChecker.notNull(y, "y");
double[] yzInterpolated = new double[_xvalues.length];
for (int i = 0; i < _xvalues.length; i++) {
yzInterpolated[i] = _yInterpolator.interpolate(_yzBundles[i], y);
}
// This is correct. We know the inputs are sorted, and because all arrays
// are either immutable and owned by this instance or are created inside
// this method no need to copy for performance.
Interpolator1DDataBundle xzBundle = new ArrayInterpolator1DDataBundle(_xvalues, yzInterpolated, true, false);
Double result = _xInterpolator.interpolate(xzBundle, x);
return result;
}
@Override
public Double getZValue(Pair<Double, Double> xy) {
return getZValue(xy.getFirst(), xy.getSecond());
}
@Override
public int hashCode() {
throw new UnsupportedOperationException("You have no reason to put this into a hashing data structure.");
}
@Override
public boolean equals(Object obj) {
throw new UnsupportedOperationException("You have no reason to compare this instance with another.");
}
}