/** * Copyright (C) 2014 - 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.OpenGammaRuntimeException; import com.opengamma.analytics.math.interpolation.data.ArrayInterpolator1DDataBundle; import com.opengamma.analytics.math.interpolation.data.InterpolationBoundedValues; import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle; import com.opengamma.util.ArgumentChecker; /** * The interpolation is linear on y^2. The interpolator is used for interpolation on variance for options. * All values of y must be positive. */ public class SquareLinearInterpolator1D extends Interpolator1D { private static final long serialVersionUID = 1L; /* Level below which the value is consider to be 0. */ private static final double EPS = 1.0E-10; @Override public Double interpolate(final Interpolator1DDataBundle data, final Double value) { Validate.notNull(value, "Value to be interpolated must not be null"); Validate.notNull(data, "Data bundle must not be null"); InterpolationBoundedValues boundedValues = data.getBoundedValues(value); double x1 = boundedValues.getLowerBoundKey(); double y1 = boundedValues.getLowerBoundValue(); if (boundedValues.getLowerBoundIndex() == data.size() - 1) { return y1; } double x2 = boundedValues.getHigherBoundKey(); double y2 = boundedValues.getHigherBoundValue(); double w = (x2 - value) / (x2 - x1); double y21 = y1 * y1; double y22 = y2 * y2; double ySq = w * y21 + (1.0 - w) * y22; return Math.sqrt(ySq); } @Override public double firstDerivative(final Interpolator1DDataBundle data, final Double value) { Validate.notNull(value, "Value to be interpolated must not be null"); Validate.notNull(data, "Data bundle must not be null"); int lowerIndex = data.getLowerBoundIndex(value); int index; if (lowerIndex == data.size() - 1) { index = data.size() - 2; } else { index = lowerIndex; } double x1 = data.getKeys()[index]; double y1 = data.getValues()[index]; double x2 = data.getKeys()[index + 1]; double y2 = data.getValues()[index + 1]; if ((y1 < EPS) && (y2 >= EPS) && (value - x1) < EPS) { // On one vertex with value 0, other vertex not 0 throw new OpenGammaRuntimeException("ask for first derivative on a value without derivative; value " + value + " is close to vertex " + x1 + " and value at vertex is " + y1); } if ((y2 < EPS) && (y1 >= EPS) && (x2 - value) < EPS) { // On one vertex with value 0, other vertex not 0 throw new OpenGammaRuntimeException("ask for first derivative on a value without derivative; value " + value + " 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 - value) / (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 public double[] getNodeSensitivitiesForValue(final Interpolator1DDataBundle data, final Double value) { Validate.notNull(value, "Value to be interpolated must not be null"); Validate.notNull(data, "Data bundle must not be null"); int n = data.size(); double[] resultSensitivity = new double[n]; InterpolationBoundedValues boundedValues = data.getBoundedValues(value); double x1 = boundedValues.getLowerBoundKey(); double y1 = boundedValues.getLowerBoundValue(); int index = boundedValues.getLowerBoundIndex(); if (index == n - 1) { resultSensitivity[n - 1] = 1.0; return resultSensitivity; } double x2 = boundedValues.getHigherBoundKey(); double y2 = boundedValues.getHigherBoundValue(); if ((value - x1) < EPS) { // On or very close to Vertex 1 resultSensitivity[index] = 1.0d; return resultSensitivity; } if ((x2 - value) < EPS) { // On or very close to Vertex 2 resultSensitivity[index + 1] = 1.0d; return resultSensitivity; } double w2 = (x2 - value) / (x2 - x1); if ((y2 < EPS) && (y1 < EPS)) { // Both values very close to 0 resultSensitivity[index] = Math.sqrt(w2); resultSensitivity[index + 1] = Math.sqrt(1.0d - w2); return resultSensitivity; } 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; resultSensitivity[index] = y1Bar; resultSensitivity[index + 1] = y2Bar; return resultSensitivity; } @Override public Interpolator1DDataBundle getDataBundle(final double[] x, final double[] y) { ArgumentChecker.notNull(y, "y"); int nY = y.length; for (int i = 0; i < nY; ++i) { ArgumentChecker.isTrue(y[i] >= 0.0, "All values in y must be positive"); } return new ArrayInterpolator1DDataBundle(x, y); } @Override public Interpolator1DDataBundle getDataBundleFromSortedArrays(final double[] x, final double[] y) { ArgumentChecker.notNull(y, "y"); int nY = y.length; for (int i = 0; i < nY; ++i) { ArgumentChecker.isTrue(y[i] >= 0.0, "All values in y must be positive"); } return new ArrayInterpolator1DDataBundle(x, y, true); } }