/** * 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 static org.testng.AssertJUnit.assertEquals; import java.util.Arrays; import java.util.Collections; import org.testng.annotations.Test; import com.opengamma.analytics.math.FunctionUtils; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.interpolation.data.ArrayInterpolator1DDataBundle; import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle; import com.opengamma.util.test.TestGroup; /** * Tests related to the exponential interpolator. */ @Test(groups = TestGroup.UNIT) public class ExponentialInterpolator1DTest { private static final Interpolator1D INTERPOLATOR = new ExponentialInterpolator1D(); private static final double EPS = 1e-4; private static final double REL_TOL = 1.0e-10; @Test(expectedExceptions = IllegalArgumentException.class) public void testNullDataBundle() { INTERPOLATOR.interpolate(null, 2.); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNullData() { INTERPOLATOR.interpolate(INTERPOLATOR.getDataBundle(Collections.<Double, Double> emptyMap()), null); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLowValue() { INTERPOLATOR.interpolate(INTERPOLATOR.getDataBundleFromSortedArrays(new double[] {1, 2}, new double[] {1, 2}), -4.); } @Test(expectedExceptions = IllegalArgumentException.class) public void testHighValue() { INTERPOLATOR.interpolate(INTERPOLATOR.getDataBundleFromSortedArrays(new double[] {1, 2}, new double[] {1, 2}), -4.); } @Test public void testDataBundleType1() { assertEquals(INTERPOLATOR.getDataBundle(new double[] {1, 2, 3}, new double[] {1, 2, 3}).getClass(), ArrayInterpolator1DDataBundle.class); } @Test public void testDataBundleType2() { assertEquals(INTERPOLATOR.getDataBundleFromSortedArrays(new double[] {1, 2, 3}, new double[] {1, 2, 3}).getClass(), ArrayInterpolator1DDataBundle.class); } /** * Recover a single exponential function */ @Test public void exponentialFunctionTest() { /* positive */ double a1 = 3.5; double b1 = 1.4; Function1D<Double, Double> func1 = createExpFunction(a1, b1); double[] xData1 = new double[] {-2.2, -3.0 / 11.0, 0.0, 0.01, 3.0, 9.5 }; double[] keys1 = new double[] {-2.05, -2.1, -1.8, -1.0 / 11.0, 0.05, 0.5, 4.5, 8.25, 9.2 }; int dataSize1 = xData1.length; double[] yData1 = new double[dataSize1]; for (int i = 0; i < dataSize1; ++i) { yData1[i] = func1.evaluate(xData1[i]); } int keySize1 = keys1.length; double[] expectedValues1 = new double[keySize1]; for (int i = 0; i < keySize1; ++i) { expectedValues1[i] = func1.evaluate(keys1[i]); } testInterpolation(xData1, yData1, keys1, expectedValues1, false); /* negative */ double a2 = -1.82; double b2 = 0.2; Function1D<Double, Double> func2 = createExpFunction(a2, b2); double[] xData2 = new double[] {-2.2, -3.0 / 11.0, 0.0, 0.01, 3.0, 12.5 }; double[] keys2 = new double[] {-2.1, -1.8, -1.0 / 11.0, 0.05, 0.5, 4.5, 8.25 }; int dataSize2 = xData2.length; double[] yData2 = new double[dataSize2]; for (int i = 0; i < dataSize2; ++i) { yData2[i] = func2.evaluate(xData2[i]); } int keySize2 = keys2.length; double[] expectedValues2 = new double[keySize2]; for (int i = 0; i < keySize2; ++i) { expectedValues2[i] = func2.evaluate(keys2[i]); } testInterpolation(xData2, yData2, keys2, expectedValues2, false); } /** * Recover piecewise exponential function */ @SuppressWarnings("unchecked") @Test public void piecewiseExponentialFunctionTest() { /* positive */ double[] a1 = new double[] {2.5, 2.2, 2.7, 5.6, 0.7 }; double[] xData1 = new double[] {-2.2, -3.0 / 11.0, 0.2, 1.1, 3.0, 9.5 }; int nIntervals = a1.length; double[] b1 = new double[nIntervals]; double[] yData1 = new double[nIntervals + 1]; // introducing b1 and yData1 such that the piecewise function becomes continuous Function1D<Double, Double>[] func1 = new Function1D[nIntervals]; b1[0] = 1.4; func1[0] = createExpFunction(a1[0], b1[0]); yData1[0] = func1[0].evaluate(xData1[0]); yData1[1] = func1[0].evaluate(xData1[1]); for (int i=1;i<nIntervals;++i) { b1[i] = b1[i - 1] - Math.log(a1[i] / a1[i - 1]) / xData1[i]; func1[i] = createExpFunction(a1[i], b1[i]); yData1[i + 1] = func1[i].evaluate(xData1[i + 1]); } double[] keys1 = new double[] {-2.05, -2.1, -1.8, -1.0 / 11.0, 0.0, 0.05, 0.5, 1.2, 3.3, 4.5, 5.2, 7.33, 8.25, 9.2 }; int keySize1 = keys1.length; double[] expectedValues1 = new double[keySize1]; for (int i = 0; i < keySize1; ++i) { int index = FunctionUtils.getLowerBoundIndex(xData1, keys1[i]); expectedValues1[i] = func1[index].evaluate(keys1[i]); } testInterpolation(xData1, yData1, keys1, expectedValues1, false); /* negative */ double[] a2 = new double[] {-2.5, -2.1, -2.2, -5.6, -1.7 }; double[] xData2 = new double[] {-2.2, -3.0 / 22.0, 0.2, 1.2, 3.0, 9.5 }; nIntervals = a2.length; double[] b2 = new double[nIntervals]; double[] yData2 = new double[nIntervals + 1]; // introducing b2 and yData2 such that the piecewise function becomes continuous Function1D<Double, Double>[] func2 = new Function1D[nIntervals]; b2[0] = 1.4; func2[0] = createExpFunction(a2[0], b2[0]); yData2[0] = func2[0].evaluate(xData2[0]); yData2[1] = func2[0].evaluate(xData2[1]); for (int i = 1; i < nIntervals; ++i) { b2[i] = b2[i - 1] - Math.log(a2[i] / a2[i - 1]) / xData2[i]; func2[i] = createExpFunction(a2[i], b2[i]); yData2[i + 1] = func2[i].evaluate(xData2[i + 1]); } double[] keys2 = new double[] {-2.05, -2.2, -1.8, -1.0 / 22.0, 0.0, 0.05, 0.5, 2.2, 3.3, 4.5, 5.2, 7.33, 8.25, 9.2 }; int keySize2 = keys2.length; double[] expectedValues2 = new double[keySize2]; for (int i = 0; i < keySize2; ++i) { int index = FunctionUtils.getLowerBoundIndex(xData2, keys2[i]); expectedValues2[i] = func2[index].evaluate(keys2[i]); } testInterpolation(xData2, yData2, keys2, expectedValues2, false); } /** * Recover flat curve */ @Test public void strightLineTest() { /* positive */ double a1 = 2.5; double b1 = 0.0; Function1D<Double, Double> func1 = createExpFunction(a1, b1); double[] xData1 = new double[] {-2.2, -1.1, -0.5, -3.0 / 11.0, 0.0, 0.01, 1.05, 2.6, 3.4, 5.1 }; double[] keys1 = new double[] {-2.1, -1.8, -1.0 / 11.0, 0.05, 0.5, 4.5 }; int dataSize1 = xData1.length; double[] yData1 = new double[dataSize1]; for (int i = 0; i < dataSize1; ++i) { yData1[i] = func1.evaluate(xData1[i]); } int keySize1 = keys1.length; double[] expectedValues1 = new double[keySize1]; for (int i = 0; i < keySize1; ++i) { expectedValues1[i] = func1.evaluate(keys1[i]); } testInterpolation(xData1, yData1, keys1, expectedValues1, false); /* negative */ double a2 = -3.82; double b2 = 0.0; Function1D<Double, Double> func2 = createExpFunction(a2, b2); double[] xData2 = new double[] {-12.0, 0.15, 1.1, 3.0, 9.2, 12.5 }; double[] keys2 = new double[] {-11.0, -5.41, 0.5, 2.22, 4.5, 5.78, 7.4, 10.1, 11.25 }; int dataSize2 = xData2.length; double[] yData2 = new double[dataSize2]; for (int i = 0; i < dataSize2; ++i) { yData2[i] = func2.evaluate(xData2[i]); } int keySize2 = keys2.length; double[] expectedValues2 = new double[keySize2]; for (int i = 0; i < keySize2; ++i) { expectedValues2[i] = func2.evaluate(keys2[i]); } testInterpolation(xData2, yData2, keys2, expectedValues2, false); } /** * Node point is treated as a point in the right interval, except the rightmost node point. */ @Test public void nodePointsTest() { /* positive */ double[] xData1 = new double[] {-7.2, -4.4, -2.1, -0.1, 5.0, 5.87 }; double[] yData1 = new double[] {3.0, 28.0, 31.2, 13.2, 19.3, 20.9 }; int dataSize1 = xData1.length; double[] keys1 = Arrays.copyOf(xData1, dataSize1 - 1); double[] expectedValues1 = Arrays.copyOf(yData1, dataSize1 - 1); testInterpolation(xData1, yData1, keys1, expectedValues1, true); testInterpolation(xData1, yData1, new double[] {xData1[dataSize1 - 1] }, new double[] {yData1[dataSize1 - 1] }, false); /* negative */ double[] xData2 = new double[] {-12.2, -3.4, -1.2, 0.26, 11.0, 25.22 }; double[] yData2 = new double[] {-2.0, -13.0, -2.2, -3.5, -9.7, -16.6 }; int dataSize2 = xData2.length; double[] keys2 = Arrays.copyOf(xData2, dataSize2 - 1); double[] expectedValues2 = Arrays.copyOf(yData2, dataSize2 - 1); testInterpolation(xData2, yData2, keys2, expectedValues2, true); testInterpolation(xData2, yData2, new double[] {xData2[dataSize2 - 1] }, new double[] {yData2[dataSize2 - 1] }, false); } /** * sign is not the same */ @Test(expectedExceptions = IllegalArgumentException.class) public void illegalYDataTest1() { double[] xData = new double[] {-6.2, -3.4, -2.1, 0.15, 5.0, 5.87 }; double[] yData = new double[] {3.0, 2.0, 1.2, -2.2, 1.3, 2.9 }; Interpolator1DDataBundle data = INTERPOLATOR.getDataBundle(xData, yData); INTERPOLATOR.interpolate(data, 1.0); } /** * sign is not the same */ @Test(expectedExceptions = IllegalArgumentException.class) public void illegalYDataTest2() { double[] xData = new double[] {-6.2, -3.4, -2.1, 0.15, 5.0, 5.87 }; double[] yData = new double[] {-3.0, -2.0, 1.2, -2.2, -1.3, -2.9 }; Interpolator1DDataBundle data = INTERPOLATOR.getDataBundleFromSortedArrays(xData, yData); INTERPOLATOR.interpolate(data, 1.0); } /** * test instances via factory */ @Test public void factoryTest() { Interpolator1D interp1 = new ExponentialInterpolator1D(); Interpolator1D interp2 = Interpolator1DFactory.EXPONENTIAL_INSTANCE; Interpolator1D interp3 = Interpolator1DFactory.getInterpolator(Interpolator1DFactory.EXPONENTIAL); Interpolator1D interp4 = Interpolator1DFactory.getInterpolator("Exponential"); assertEquals(interp1, interp2); assertEquals(interp1, interp3); assertEquals(interp1, interp4); } private void testInterpolation(double[] xData, double[] yData, double[] keys, double[] expectedValues, boolean isNode) { Interpolator1DDataBundle data = INTERPOLATOR.getDataBundleFromSortedArrays(xData, yData); int dataSize = xData.length; int keySize = keys.length; for (int i = 0; i < keySize; ++i) { /* Test interpolant value at key */ double result = INTERPOLATOR.interpolate(data, keys[i]); assertEqualsRelative(expectedValues[i], result, REL_TOL); /* Test gradient at key */ double expectedGrad = 0.0; if (keys[i] + EPS > xData[dataSize - 1]) { double downDown = INTERPOLATOR.interpolate(data, keys[i] - 2.0 * EPS); double down = INTERPOLATOR.interpolate(data, keys[i] - EPS); expectedGrad = 0.5 * (3.0 * result + downDown - 4.0 * down) / EPS; } else if (keys[i] - EPS < xData[0] || isNode) { double up = INTERPOLATOR.interpolate(data, keys[i] + EPS); double upUp = INTERPOLATOR.interpolate(data, keys[i] + 2.0 * EPS); expectedGrad = 0.5 * (4.0 * up - 3.0 * result - upUp) / EPS; } else { double up = INTERPOLATOR.interpolate(data, keys[i] + EPS); double down = INTERPOLATOR.interpolate(data, keys[i] - EPS); expectedGrad = 0.5 * (up - down) / EPS; } assertEqualsRelative(expectedGrad, INTERPOLATOR.firstDerivative(data, keys[i]), EPS); /* Test node sensitivities at key */ double[] sense = INTERPOLATOR.getNodeSensitivitiesForValue(data, keys[i]); for (int j = 0; j < dataSize; ++j) { double[] yDataUp = Arrays.copyOf(yData, dataSize); double[] yDataDw = Arrays.copyOf(yData, dataSize); yDataUp[j] += EPS; yDataDw[j] -= EPS; Interpolator1DDataBundle dataUp = INTERPOLATOR.getDataBundleFromSortedArrays(xData, yDataUp); Interpolator1DDataBundle dataDw = INTERPOLATOR.getDataBundleFromSortedArrays(xData, yDataDw); double valueUp = INTERPOLATOR.interpolate(dataUp, keys[i]); double valueDw = INTERPOLATOR.interpolate(dataDw, keys[i]); double expectedSense = 0.5 * (valueUp - valueDw) / EPS; assertEquals(expectedSense, sense[j], EPS); } } } private void assertEqualsRelative(double expected, double obtained, double relativeTol) { assertEquals(expected, obtained, Math.max(Math.abs(expected), 1.0) * relativeTol); } private Function1D<Double, Double> createExpFunction(final double a, final double b) { return new Function1D<Double, Double>() { @Override public Double evaluate(Double value) { return a * Math.exp(b * value); } }; } }