All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.math.impl.interpolation.SemiLocalCubicSplineInterpolator Maven / Gradle / Ivy

There is a newer version: 2.12.46
Show newest version
/*
 * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.math.impl.interpolation;

import java.util.Arrays;
import java.util.stream.IntStream;

import com.google.common.primitives.Doubles;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.DoubleArrayMath;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;

/**
 * Cubic spline interpolation based on 
 * H. Akima, "A New Method of Interpolation and Smooth Curve Fitting Based on Local Procedures," 
 * Journal of the Association for Computing Machinery, Vol 17, no 4, October 1970, 589-602
 */
public class SemiLocalCubicSplineInterpolator extends PiecewisePolynomialInterpolator {

  private static final double ERROR = 1.e-13;
  private static final double EPS = 1.e-7;
  private static final double SMALL = 1.e-14;
  private final HermiteCoefficientsProvider _solver = new HermiteCoefficientsProvider();

  @Override
  public PiecewisePolynomialResult interpolate(double[] xValues, double[] yValues) {

    ArgChecker.notNull(xValues, "xValues");
    ArgChecker.notNull(yValues, "yValues");

    ArgChecker.isTrue(xValues.length == yValues.length, "(xValues length = yValues length) should be true");
    ArgChecker.isTrue(xValues.length > 2, "Data points should be >= 3");

    int nDataPts = xValues.length;

    for (int i = 0; i < nDataPts; ++i) {
      ArgChecker.isFalse(Double.isNaN(xValues[i]), "xValues containing NaN");
      ArgChecker.isFalse(Double.isInfinite(xValues[i]), "xValues containing Infinity");
      ArgChecker.isFalse(Double.isNaN(yValues[i]), "yValues containing NaN");
      ArgChecker.isFalse(Double.isInfinite(yValues[i]), "yValues containing Infinity");
    }

    double[] xValuesSrt = Arrays.copyOf(xValues, nDataPts);
    double[] yValuesSrt = Arrays.copyOf(yValues, nDataPts);
    DoubleArrayMath.sortPairs(xValuesSrt, yValuesSrt);
    ArgChecker.noDuplicatesSorted(xValuesSrt, "xValues");

    double[] intervals = _solver.intervalsCalculator(xValuesSrt);
    double[] slopes = _solver.slopesCalculator(yValuesSrt, intervals);
    double[] first = firstDerivativeCalculator(slopes);
    double[][] coefs = _solver.solve(yValuesSrt, intervals, slopes, first);

    for (int i = 0; i < nDataPts - 1; ++i) {
      double ref = 0.;
      for (int j = 0; j < 4; ++j) {
        ref += coefs[i][j] * Math.pow(intervals[i], 3 - j);
        ArgChecker.isFalse(Double.isNaN(coefs[i][j]), "Too large input");
        ArgChecker.isFalse(Double.isInfinite(coefs[i][j]), "Too large input");
      }
      double bound = Math.max(Math.abs(ref) + Math.abs(yValuesSrt[i + 1]), 1.e-1);
      ArgChecker.isTrue(Math.abs(ref - yValuesSrt[i + 1]) < ERROR * bound,
          "Input is too large/small or data points are too close");
    }

    return new PiecewisePolynomialResult(DoubleArray.copyOf(xValuesSrt), DoubleMatrix.copyOf(coefs), 4, 1);
  }

  @Override
  public PiecewisePolynomialResult interpolate(double[] xValues, double[][] yValuesMatrix) {
    ArgChecker.notNull(xValues, "xValues");
    ArgChecker.notNull(yValuesMatrix, "yValuesMatrix");

    ArgChecker.isTrue(xValues.length == yValuesMatrix[0].length,
        "(xValues length = yValuesMatrix's row vector length) should be true");
    ArgChecker.isTrue(xValues.length > 2, "Data points should be >= 3");

    int nDataPts = xValues.length;
    int yValuesLen = yValuesMatrix[0].length;
    int dim = yValuesMatrix.length;

    for (int i = 0; i < nDataPts; ++i) {
      ArgChecker.isFalse(Double.isNaN(xValues[i]), "xValues containing NaN");
      ArgChecker.isFalse(Double.isInfinite(xValues[i]), "xValues containing Infinity");
    }
    for (int i = 0; i < yValuesLen; ++i) {
      for (int j = 0; j < dim; ++j) {
        ArgChecker.isFalse(Double.isNaN(yValuesMatrix[j][i]), "yValuesMatrix containing NaN");
        ArgChecker.isFalse(Double.isInfinite(yValuesMatrix[j][i]), "yValuesMatrix containing Infinity");
      }
    }

    double[] xValuesSrt = Arrays.copyOf(xValues, nDataPts);
    int[] sortedPositions = IntStream.range(0, nDataPts).toArray();
    DoubleArrayMath.sortPairs(xValuesSrt, sortedPositions);
    ArgChecker.noDuplicatesSorted(xValuesSrt, "xValues");
    DoubleMatrix[] coefMatrix = new DoubleMatrix[dim];

    for (int i = 0; i < dim; ++i) {
      double[] yValuesSrt = DoubleArrayMath.reorderedCopy(yValuesMatrix[i], sortedPositions);

      double[] intervals = _solver.intervalsCalculator(xValuesSrt);
      double[] slopes = _solver.slopesCalculator(yValuesSrt, intervals);
      double[] first = firstDerivativeCalculator(slopes);

      coefMatrix[i] = DoubleMatrix.copyOf(_solver.solve(yValuesSrt, intervals, slopes, first));

      for (int k = 0; k < intervals.length; ++k) {
        double ref = 0.;
        for (int j = 0; j < 4; ++j) {
          ref += coefMatrix[i].get(k, j) * Math.pow(intervals[k], 3 - j);
          ArgChecker.isFalse(Double.isNaN(coefMatrix[i].get(k, j)), "Too large input");
          ArgChecker.isFalse(Double.isInfinite(coefMatrix[i].get(k, j)), "Too large input");
        }
        double bound = Math.max(Math.abs(ref) + Math.abs(yValuesSrt[k + 1]), 1.e-1);
        ArgChecker.isTrue(Math.abs(ref - yValuesSrt[k + 1]) < ERROR * bound,
            "Input is too large/small or data points are too close");
      }
    }

    int nIntervals = coefMatrix[0].rowCount();
    int nCoefs = coefMatrix[0].columnCount();
    double[][] resMatrix = new double[dim * nIntervals][nCoefs];

    for (int i = 0; i < nIntervals; ++i) {
      for (int j = 0; j < dim; ++j) {
        resMatrix[dim * i + j] = coefMatrix[j].row(i).toArray();
      }
    }

    return new PiecewisePolynomialResult(DoubleArray.copyOf(xValuesSrt), DoubleMatrix.copyOf(resMatrix), nCoefs, dim);
  }

  @Override
  public PiecewisePolynomialResultsWithSensitivity interpolateWithSensitivity(double[] xValues, double[] yValues) {
    ArgChecker.notNull(xValues, "xValues");
    ArgChecker.notNull(yValues, "yValues");

    ArgChecker.isTrue(xValues.length == yValues.length, "(xValues length = yValues length) should be true");
    ArgChecker.isTrue(xValues.length > 2, "Data points should be >= 3");

    int nDataPts = xValues.length;

    for (int i = 0; i < nDataPts; ++i) {
      ArgChecker.isFalse(Double.isNaN(xValues[i]), "xValues containing NaN");
      ArgChecker.isFalse(Double.isInfinite(xValues[i]), "xValues containing Infinity");
      ArgChecker.isFalse(Double.isNaN(yValues[i]), "yValues containing NaN");
      ArgChecker.isFalse(Double.isInfinite(yValues[i]), "yValues containing Infinity");
    }

    ArgChecker.noDuplicates(xValues, "xValues");

    double[] intervals = _solver.intervalsCalculator(xValues);
    double[] slopes = _solver.slopesCalculator(yValues, intervals);
    double[][] slopeSensitivity = _solver.slopeSensitivityCalculator(intervals);
    DoubleArray[] firstWithSensitivity =
        firstDerivativeWithSensitivityCalculator(yValues, intervals, slopes, slopeSensitivity);
    DoubleMatrix[] resMatrix = _solver.solveWithSensitivity(yValues, intervals, slopes, slopeSensitivity, firstWithSensitivity);

    for (int k = 0; k < nDataPts; k++) {
      DoubleMatrix m = resMatrix[k];
      int rows = m.rowCount();
      int cols = m.columnCount();
      for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
          ArgChecker.isTrue(Doubles.isFinite(m.get(i, j)), "Matrix contains a NaN or infinite");
        }
      }
    }

    DoubleMatrix coefMatrix = resMatrix[0];
    for (int i = 0; i < nDataPts - 1; ++i) {
      double ref = 0.;
      for (int j = 0; j < 4; ++j) {
        ref += coefMatrix.get(i, j) * Math.pow(intervals[i], 3 - j);
      }
      double bound = Math.max(Math.abs(ref) + Math.abs(yValues[i + 1]), 1.e-1);
      ArgChecker.isTrue(Math.abs(ref - yValues[i + 1]) < ERROR * bound,
          "Input is too large/small or data points are too close");
    }
    DoubleMatrix[] coefSenseMatrix = new DoubleMatrix[nDataPts - 1];
    System.arraycopy(resMatrix, 1, coefSenseMatrix, 0, nDataPts - 1);
    int nCoefs = coefMatrix.columnCount();

    return new PiecewisePolynomialResultsWithSensitivity(
        DoubleArray.copyOf(xValues), coefMatrix, nCoefs, 1, coefSenseMatrix);
  }

  private double[] firstDerivativeCalculator(double[] slopes) {
    int nData = slopes.length + 1;
    double[] res = new double[nData];

    double[] slopesExt = getExtraPoints(slopes);
    for (int i = 0; i < nData; ++i) {
      if (Math.abs(slopesExt[i + 3] - slopesExt[i + 2]) == 0.) {
        if (Math.abs(slopesExt[i + 1] - slopesExt[i]) == 0.) {
          res[i] = 0.5 * (slopesExt[i + 1] + slopesExt[i + 2]);
        } else {
          res[i] = slopesExt[i + 2];
        }
      } else {
        if (Math.abs(slopesExt[i + 1] - slopesExt[i]) == 0.) {
          res[i] = slopesExt[i];
        } else {
          res[i] = (Math.abs(slopesExt[i + 3] - slopesExt[i + 2]) * slopesExt[i + 1] +
              Math.abs(slopesExt[i + 1] - slopesExt[i]) * slopesExt[i + 2]) /
              (Math.abs(slopesExt[i + 3] - slopesExt[i + 2]) + Math.abs(slopesExt[i + 1] - slopesExt[i]));
        }
      }
    }

    return res;
  }

  private DoubleArray[] firstDerivativeWithSensitivityCalculator(
      double[] yValues,
      double[] intervals,
      double[] slopes,
      double[][] slopeSensitivity) {

    int nData = yValues.length;
    double[] slopesExt = getExtraPoints(slopes);
    double[][] slopeSensitivityExtTransp = new double[nData][nData + 3];
    DoubleArray[] res = new DoubleArray[nData + 1];
    DoubleMatrix senseMat = DoubleMatrix.copyOf(slopeSensitivity);

    for (int i = 0; i < nData; ++i) {
      slopeSensitivityExtTransp[i] = getExtraPoints(senseMat.column(i).toArray());
    }

    DoubleArray[] modSlopesWithSensitivity = modSlopesWithSensitivityCalculator(slopesExt, slopeSensitivityExtTransp);
    DoubleArray modSlopesWithSense0 = modSlopesWithSensitivity[0];

    double[] first = new double[nData];
    for (int i = 0; i < nData; ++i) {
      double[] tmp = new double[nData];
      double den = (modSlopesWithSense0.get(i + 2) + modSlopesWithSense0.get(i));
      if (den == 0.) {
        first[i] = 0.5 * (slopesExt[i + 1] + slopesExt[i + 2]);

        Arrays.fill(tmp, 0.);
        double[] yValuesUp = Arrays.copyOf(yValues, nData);
        double[] yValuesDw = Arrays.copyOf(yValues, nData);
        for (int j = 0; j < nData; ++j) {
          double div = Math.abs(yValues[j]) < SMALL ? EPS : yValues[j] * EPS;
          yValuesUp[j] = Math.abs(yValues[j]) < SMALL ? EPS : yValues[j] * (1. + EPS);
          yValuesDw[j] = Math.abs(yValues[j]) < SMALL ? -EPS : yValues[j] * (1. - EPS);
          double firstUp = firstDerivativeCalculator(_solver.slopesCalculator(yValuesUp, intervals))[i];
          double firstDw = firstDerivativeCalculator(_solver.slopesCalculator(yValuesDw, intervals))[i];
          tmp[j] = 0.5 * (firstUp - firstDw) / div;
          yValuesUp[j] = yValues[j];
          yValuesDw[j] = yValues[j];
        }
      } else {
        first[i] =
            modSlopesWithSense0.get(i + 2) * slopesExt[i + 1] / den +
                modSlopesWithSense0.get(i) * slopesExt[i + 2] / den;
        for (int k = 0; k < nData; ++k) {
          tmp[k] =
              (modSlopesWithSense0.get(i + 2) * slopeSensitivityExtTransp[k][i + 1] +
                  modSlopesWithSense0.get(i) * slopeSensitivityExtTransp[k][i + 2]) / den
              + (slopesExt[i + 2] - slopesExt[i + 1]) *
                  (modSlopesWithSense0.get(i + 2) * modSlopesWithSensitivity[i + 1].get(k) -
                  modSlopesWithSense0.get(i) * modSlopesWithSensitivity[i + 3].get(k)) / den / den;
        }
      }
      res[i + 1] = DoubleArray.copyOf(tmp);
    }
    res[0] = DoubleArray.copyOf(first);

    return res;
  }

  private DoubleArray[] modSlopesWithSensitivityCalculator(double[] slopesExt, double[][] slopeSensitivityExtTransp) {
    int nData = slopesExt.length - 3;
    double[] modSlopes = new double[nData + 2];
    DoubleArray[] res = new DoubleArray[nData + 3];

    for (int i = 0; i < nData + 2; ++i) {
      double[] tmp = new double[nData];
      if (slopesExt[i + 1] == slopesExt[i]) {
        modSlopes[i] = 0.;
        Arrays.fill(tmp, 0.);
      } else {
        if (slopesExt[i + 1] > slopesExt[i]) {
          modSlopes[i] = slopesExt[i + 1] - slopesExt[i];
          for (int k = 0; k < nData; ++k) {
            tmp[k] = slopeSensitivityExtTransp[k][i + 1] - slopeSensitivityExtTransp[k][i];
          }
        } else {
          modSlopes[i] = -slopesExt[i + 1] + slopesExt[i];
          for (int k = 0; k < nData; ++k) {
            tmp[k] = -slopeSensitivityExtTransp[k][i + 1] + slopeSensitivityExtTransp[k][i];
          }
        }
      }
      res[i + 1] = DoubleArray.copyOf(tmp);
    }
    res[0] = DoubleArray.copyOf(modSlopes);

    return res;
  }

  private double[] getExtraPoints(double[] data) {
    int nData = data.length + 1;
    double[] res = new double[nData + 3];
    res[0] = 3. * data[0] - 2. * data[1];
    res[1] = 2. * data[0] - data[1];
    res[nData + 1] = 2. * data[nData - 2] - data[nData - 3];
    res[nData + 2] = 3 * data[nData - 2] - 2. * data[nData - 3];
    System.arraycopy(data, 0, res, 2, nData - 1);

    return res;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy