@Test(expectedExceptions = IllegalArgumentException.class)
  public void nullxDiffTest() {
    double[] xValues = new double[] {1, 2, 3, 4};
    DoubleMatrix2D coefsMatrix =
        new DoubleMatrix2D(
            new double[][] {
              {1., -3., 3., -1},
              {0., 5., -20., 20},
              {1., 0., 0., 0.},
              {0., 5., -10., 5},
              {1., 3., 3., 1.},
              {0., 5., 0., 0.}
            });
    double[] xKeys = new double[] {-2, 1, 2, 2.5};
    final int dim = 2;
    final int nCoefs = 4;

    xKeys = null;

    PiecewisePolynomialResult pp =
        new PiecewisePolynomialResult(new DoubleMatrix1D(xValues), coefsMatrix, nCoefs, dim);
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    function.differentiate(pp, xKeys);
  }
  @Test
  public void evaluateAllTest() {
    final double[] xValues = new double[] {1, 2, 3, 4};
    final DoubleMatrix2D coefsMatrix =
        new DoubleMatrix2D(
            new double[][] {
              {1., -3., 3., -1},
              {0., 5., -20., 20},
              {1., 0., 0., 0.},
              {0., 5., -10., 5},
              {1., 3., 3., 1.},
              {0., 5., 0., 0.}
            });
    final double[][] xKeys = new double[][] {{-2, 1, 2, 2.5}, {1.5, 7. / 3., 29. / 7., 5.}};
    final double[][][] valuesExp =
        new double[][][] {
          {{-64., -1., 0., 1. / 8.}, {-1. / 8., 1. / 27., 3375. / 7. / 7. / 7., 27.}},
          {{125., 20., 5., 5. / 4.}, {45. / 4., 20. / 9., 2240. / 7. / 7. / 7., 20.}}
        };
    final int dim = 2;
    final int nCoefs = 4;
    final int keyLength = xKeys[0].length;
    final int keyDim = xKeys.length;

    PiecewisePolynomialResult pp =
        new PiecewisePolynomialResult(new DoubleMatrix1D(xValues), coefsMatrix, nCoefs, dim);
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    final DoubleMatrix2D[] valuesResMat = function.evaluate(pp, xKeys);
    for (int i = 0; i < dim; ++i) {
      for (int k = 0; k < keyDim; ++k) {
        for (int j = 0; j < keyLength; ++j) {
          final double ref = valuesExp[i][k][j] == 0. ? 1. : Math.abs(valuesExp[i][k][j]);
          assertEquals(valuesResMat[i].getData()[k][j], valuesExp[i][k][j], ref * EPS);
        }
      }
    }

    final double[][] valuesRes = function.evaluate(pp, xKeys[0]).getData();
    for (int i = 0; i < dim; ++i) {
      for (int j = 0; j < keyLength; ++j) {
        final double ref = valuesExp[i][0][j] == 0. ? 1. : Math.abs(valuesExp[i][0][j]);
        assertEquals(valuesRes[i][j], valuesExp[i][0][j], ref * EPS);
      }
    }

    double[] valuesResVec = function.evaluate(pp, xKeys[0][0]).getData();
    for (int i = 0; i < dim; ++i) {
      final double ref = valuesExp[i][0][0] == 0. ? 1. : Math.abs(valuesExp[i][0][0]);
      assertEquals(valuesResVec[i], valuesExp[i][0][0], ref * EPS);
    }

    valuesResVec = function.evaluate(pp, xKeys[0][3]).getData();
    for (int i = 0; i < dim; ++i) {
      final double ref = valuesExp[i][0][3] == 0. ? 1. : Math.abs(valuesExp[i][0][3]);
      assertEquals(valuesResVec[i], valuesExp[i][0][3], ref * EPS);
    }
  }
  @Test
  public void linearAllTest() {

    final double[] knots = new double[] {1., 4.};
    final DoubleMatrix2D coefsMatrix = new DoubleMatrix2D(new double[][] {{0., 1., 1.}});
    final double[] xKeys = new double[] {-2, 1., 2.5, 4.};
    final double[] initials = new double[] {-0.5, 1., 2.5, 5.};
    final int nKeys = xKeys.length;
    final int nInit = initials.length;

    final double[] valuesExp = new double[] {-2, 1, 2.5, 4.};
    final double[][] integrateExp = new double[nInit][nKeys];
    for (int i = 0; i < nInit; ++i) {
      for (int j = 0; j < nKeys; ++j) {
        integrateExp[i][j] = 0.5 * (xKeys[j] * xKeys[j] - initials[i] * initials[i]);
      }
    }
    final double[] differentiateExp = new double[] {1., 1., 1., 1.};

    PiecewisePolynomialResult result =
        new PiecewisePolynomialResult(new DoubleMatrix1D(knots), coefsMatrix, 3, 1);
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    final double[] values = function.evaluate(result, xKeys).getData()[0];
    final double[] differentiate = function.differentiate(result, xKeys).getData()[0];
    final double[][] integrate = new double[nInit][nKeys];
    for (int i = 0; i < nInit; ++i) {
      for (int j = 0; j < nKeys; ++j) {
        integrate[i][j] = function.integrate(result, initials[i], xKeys).getData()[j];
      }
    }

    for (int i = 0; i < nKeys; ++i) {
      final double ref = valuesExp[i] == 0. ? 1. : Math.abs(valuesExp[i]);
      assertEquals(values[i], valuesExp[i], ref * EPS);
    }

    for (int i = 0; i < nKeys; ++i) {
      final double ref = differentiateExp[i] == 0. ? 1. : Math.abs(differentiateExp[i]);
      assertEquals(differentiate[i], differentiateExp[i], ref * EPS);
    }

    for (int j = 0; j < nInit; ++j) {
      for (int i = 0; i < nKeys; ++i) {
        final double ref = integrateExp[j][i] == 0. ? 1. : Math.abs(integrateExp[j][i]);
        assertEquals(integrate[j][i], integrateExp[j][i], ref * EPS);
      }
    }
  }
  @Test(expectedExceptions = IllegalArgumentException.class)
  public void NaNxIntMultiTest() {
    double[] xValues = new double[] {1, 2, 3, 4};
    DoubleMatrix2D coefsMatrix =
        new DoubleMatrix2D(new double[][] {{1., -3., 3., -1}, {1., 0., 0., 0.}, {1., 3., 3., 1.}});
    double[] xKeys = new double[] {1.5, 7. / 3., 29. / 7., Double.NaN};
    final int dim = 1;
    final int nCoefs = 4;

    PiecewisePolynomialResult pp =
        new PiecewisePolynomialResult(new DoubleMatrix1D(xValues), coefsMatrix, nCoefs, dim);
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    function.integrate(pp, 1., xKeys);
  }
  @Test(expectedExceptions = IllegalArgumentException.class)
  public void nullpIntegrateTest() {
    final double[] xValues = new double[] {1, 2, 3, 4};
    final DoubleMatrix2D coefsMatrix =
        new DoubleMatrix2D(new double[][] {{1., -3., 3., -1}, {1., 0., 0., 0.}, {1., 3., 3., 1.}});
    final double[][] xKeys = new double[][] {{-2, 1, 2, 2.5}, {1.5, 7. / 3., 29. / 7., 5.}};
    final int dim = 1;
    final int nCoefs = 4;

    PiecewisePolynomialResult pp =
        new PiecewisePolynomialResult(new DoubleMatrix1D(xValues), coefsMatrix, nCoefs, dim);
    pp = null;
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    function.integrate(pp, 1., xKeys[0][0]);
  }
 @Override
 public double firstDerivative(final Interpolator1DDataBundle data, final Double value) {
   Validate.notNull(value, "value");
   Validate.notNull(data, "data bundle");
   Validate.isTrue(data instanceof Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle);
   final Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle polyData =
       (Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle) data;
   final DoubleMatrix1D res = FUNC.differentiate(polyData.getPiecewisePolynomialResult(), value);
   return res.getEntry(0);
 }
  @Test(expectedExceptions = IllegalArgumentException.class)
  public void NaNxEvaluateMultiTest() {
    double[] xValues = new double[] {1, 2, 3, 4};
    DoubleMatrix2D coefsMatrix =
        new DoubleMatrix2D(
            new double[][] {
              {1., -3., 3., -1},
              {0., 5., -20., 20},
              {1., 0., 0., 0.},
              {0., 5., -10., 5},
              {1., 3., 3., 1.},
              {0., 5., 0., 0.}
            });
    double[][] xKeys = new double[][] {{-2, 1, Double.NaN, 2.5}, {1.5, 7. / 3., 29. / 7., 5.}};
    final int dim = 2;
    final int nCoefs = 4;

    PiecewisePolynomialResult pp =
        new PiecewisePolynomialResult(new DoubleMatrix1D(xValues), coefsMatrix, nCoefs, dim);
    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();

    function.evaluate(pp, xKeys[0]);
  }
  @Override
  public double[] getNodeSensitivitiesForValue(
      final Interpolator1DDataBundle data, final Double value) {
    Validate.notNull(value, "value");
    Validate.notNull(data, "data bundle");
    Validate.isTrue(data instanceof Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle);
    final Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle polyData =
        (Interpolator1DPiecewisePoynomialWithExtraKnotsDataBundle) data;
    final int nData = polyData.size();
    final double[] res = new double[nData];

    final double eps = polyData.getEps();
    final double small = polyData.getSmall();
    for (int i = 0; i < nData; ++i) {
      final double den =
          Math.abs(polyData.getValues()[i]) < small ? eps : polyData.getValues()[i] * eps;
      final double up =
          FUNC.evaluate(polyData.getPiecewisePolynomialResultUp()[i], value).getData()[0];
      final double dw =
          FUNC.evaluate(polyData.getPiecewisePolynomialResultDw()[i], value).getData()[0];
      res[i] = 0.5 * (up - dw) / den;
    }
    return res;
  }
  @Test
  public void crossDerivativeTest() {
    double[] x0Values = new double[] {1., 2., 3., 4.};
    double[] x1Values = new double[] {-1., 0., 1., 2., 3.};
    final int n0Data = x0Values.length;
    final int n1Data = x1Values.length;
    double[][] yValues =
        new double[][] {
          {
            1.0, -1.0, 0.0, 1.0, 0.0,
          },
          {1.0, -1.0, 0.0, 1.0, -2.0},
          {1.0, -2.0, 0.0, -2.0, -2.0},
          {-1.0, -1.0, -2.0, -2.0, -1.0}
        };

    NaturalSplineInterpolator method = new NaturalSplineInterpolator();
    PiecewisePolynomialInterpolator2D interp = new BicubicSplineInterpolator(method);
    PiecewisePolynomialResult2D result = interp.interpolate(x0Values, x1Values, yValues);

    final int n0IntExp = n0Data - 1;
    final int n1IntExp = n1Data - 1;
    final int orderExp = 4;

    final int n0Keys = 51;
    final int n1Keys = 61;
    double[] x0Keys = new double[n0Keys];
    double[] x1Keys = new double[n1Keys];
    for (int i = 0; i < n0Keys; ++i) {
      x0Keys[i] = 0. + 5. * i / (n0Keys - 1);
    }
    for (int i = 0; i < n1Keys; ++i) {
      x1Keys[i] = -2. + 6. * i / (n1Keys - 1);
    }

    assertEquals(result.getNumberOfIntervals()[0], n0IntExp);
    assertEquals(result.getNumberOfIntervals()[1], n1IntExp);
    assertEquals(result.getOrder()[0], orderExp);
    assertEquals(result.getOrder()[1], orderExp);

    for (int i = 0; i < n0Data; ++i) {
      final double ref = Math.abs(x0Values[i]) == 0. ? 1. : Math.abs(x0Values[i]);
      assertEquals(result.getKnots0().getData()[i], x0Values[i], ref * EPS);
      assertEquals(result.getKnots2D().get(0).getData()[i], x0Values[i], ref * EPS);
    }
    for (int i = 0; i < n1Data; ++i) {
      final double ref = Math.abs(x1Values[i]) == 0. ? 1. : Math.abs(x1Values[i]);
      assertEquals(result.getKnots1().getData()[i], x1Values[i], ref * EPS);
      assertEquals(result.getKnots2D().get(1).getData()[i], x1Values[i], ref * EPS);
    }

    for (int i = 0; i < n0Data - 1; ++i) {
      for (int j = 0; j < n1Data - 1; ++j) {
        final double ref = Math.abs(yValues[i][j]) == 0. ? 1. : Math.abs(yValues[i][j]);
        assertEquals(
            result.getCoefs()[i][j].getData()[orderExp - 1][orderExp - 1],
            yValues[i][j],
            ref * EPS);
      }
    }

    double[][] resValues =
        interp.interpolate(x0Values, x1Values, yValues, x0Values, x1Values).getData();
    final PiecewisePolynomialFunction2D func2D = new PiecewisePolynomialFunction2D();
    double[][] resDiffX0 = func2D.differentiateX0(result, x0Values, x1Values).getData();
    double[][] resDiffX1 = func2D.differentiateX1(result, x0Values, x1Values).getData();

    final PiecewisePolynomialFunction1D func1D = new PiecewisePolynomialFunction1D();
    double[][] expDiffX0 =
        func1D
            .differentiate(
                method.interpolate(
                    x0Values, OG_ALGEBRA.getTranspose(new DoubleMatrix2D(yValues)).getData()),
                x0Values)
            .getData();
    double[][] expDiffX1 =
        func1D.differentiate(method.interpolate(x1Values, yValues), x1Values).getData();

    for (int i = 0; i < n0Data; ++i) {
      for (int j = 0; j < n1Data; ++j) {
        final double expVal = expDiffX1[i][j];
        final double ref = Math.abs(expVal) == 0. ? 1. : Math.abs(expVal);
        assertEquals(resDiffX1[i][j], expVal, ref * EPS);
      }
    }
    //    System.out.println(new DoubleMatrix2D(expDiffX0));
    //    System.out.println(new DoubleMatrix2D(resDiffX0));
    for (int i = 0; i < n0Data; ++i) {
      for (int j = 0; j < n1Data; ++j) {
        final double expVal = expDiffX0[j][i];
        final double ref = Math.abs(expVal) == 0. ? 1. : Math.abs(expVal);
        assertEquals(resDiffX0[i][j], expVal, ref * EPS);
      }
    }

    for (int i = 0; i < n0Data; ++i) {
      for (int j = 0; j < n1Data; ++j) {
        final double expVal = yValues[i][j];
        final double ref = Math.abs(expVal) == 0. ? 1. : Math.abs(expVal);
        assertEquals(resValues[i][j], expVal, ref * EPS);
      }
    }
  }
  /** Sample function is f(x) = (x-1)^4 */
  @Test
  public void GeneralIntegrateDifferentiateTest() {
    final double[] knots = new double[] {1., 2., 3., 4};
    final double[][] coefMat =
        new double[][] {{1., 0., 0., 0., 0.}, {1., 4., 6., 4., 1.}, {1., 8., 24., 32., 16.}};
    final double[] xKeys = new double[] {-2, 1, 2.5, 4.};
    final double[] initials = new double[] {1., 2.5, 23. / 7., 7.};
    final int nKeys = xKeys.length;
    final int nInit = initials.length;

    final double[][] integrateExp = new double[nInit][nKeys];
    for (int i = 0; i < nInit; ++i) {
      for (int j = 0; j < nKeys; ++j) {
        integrateExp[i][j] = Math.pow(xKeys[j] - 1., 5.) / 5. - Math.pow(initials[i] - 1., 5.) / 5.;
      }
    }
    final double[] differentiateExp = new double[] {-108., 0., 27. / 2., 108.};
    final double[] differentiateTwiceExp = new double[nKeys];
    for (int i = 0; i < nKeys; ++i) {
      differentiateTwiceExp[i] = 12. * (xKeys[i] - 1.) * (xKeys[i] - 1.);
    }

    PiecewisePolynomialFunction1D function = new PiecewisePolynomialFunction1D();
    PiecewisePolynomialResult result =
        new PiecewisePolynomialResult(new DoubleMatrix1D(knots), new DoubleMatrix2D(coefMat), 5, 1);

    final double[] differentiate = function.differentiate(result, xKeys).getData()[0];
    final double[] differentiateTwice = function.differentiateTwice(result, xKeys).getData()[0];
    final double[][] integrate = new double[nInit][nKeys];
    for (int i = 0; i < nInit; ++i) {
      for (int j = 0; j < nKeys; ++j) {
        integrate[i][j] = function.integrate(result, initials[i], xKeys).getData()[j];
      }
    }

    for (int i = 0; i < nKeys; ++i) {
      final double ref = differentiateExp[i] == 0. ? 1. : Math.abs(differentiateExp[i]);
      assertEquals(differentiate[i], differentiateExp[i], ref * EPS);
    }
    for (int i = 0; i < nKeys; ++i) {
      final double ref = differentiateTwiceExp[i] == 0. ? 1. : Math.abs(differentiateTwiceExp[i]);
      assertEquals(differentiateTwice[i], differentiateTwiceExp[i], ref * EPS);
    }

    for (int j = 0; j < nInit; ++j) {
      for (int i = 0; i < nKeys; ++i) {
        final double ref = integrateExp[j][i] == 0. ? 1. : Math.abs(integrateExp[j][i]);
        assertEquals(integrate[j][i], integrateExp[j][i], ref * EPS);
      }
    }

    {
      final double ref = differentiateExp[0] == 0. ? 1. : Math.abs(differentiateExp[0]);
      assertEquals(
          function.differentiate(result, xKeys[0]).getData()[0], differentiateExp[0], ref * EPS);
    }
    {
      final double ref = differentiateExp[3] == 0. ? 1. : Math.abs(differentiateExp[3]);
      assertEquals(
          function.differentiate(result, xKeys[3]).getData()[0], differentiateExp[3], ref * EPS);
    }
    {
      final double ref = differentiateTwiceExp[0] == 0. ? 1. : Math.abs(differentiateTwiceExp[0]);
      assertEquals(
          function.differentiateTwice(result, xKeys[0]).getData()[0],
          differentiateTwiceExp[0],
          ref * EPS);
    }
    {
      final double ref = differentiateTwiceExp[3] == 0. ? 1. : Math.abs(differentiateTwiceExp[3]);
      assertEquals(
          function.differentiateTwice(result, xKeys[3]).getData()[0],
          differentiateTwiceExp[3],
          ref * EPS);
    }
    {
      final double ref = integrateExp[0][0] == 0. ? 1. : Math.abs(integrateExp[0][0]);
      assertEquals(
          function.integrate(result, initials[0], xKeys[0]), integrateExp[0][0], ref * EPS);
    }
    {
      final double ref = integrateExp[0][3] == 0. ? 1. : Math.abs(integrateExp[0][3]);
      assertEquals(
          function.integrate(result, initials[0], xKeys[3]), integrateExp[0][3], ref * EPS);
    }
    {
      final double ref = integrateExp[3][0] == 0. ? 1. : Math.abs(integrateExp[3][0]);
      assertEquals(
          function.integrate(result, initials[3], xKeys[0]), integrateExp[3][0], ref * EPS);
    }
    {
      final double ref = integrateExp[1][0] == 0. ? 1. : Math.abs(integrateExp[1][0]);
      assertEquals(
          function.integrate(result, initials[1], xKeys[0]), integrateExp[1][0], ref * EPS);
    }
  }
  @Override
  public PiecewisePolynomialResult interpolate(final double[] xValues, final double[] yValues) {
    ArgumentChecker.notNull(xValues, "xValues");
    ArgumentChecker.notNull(yValues, "yValues");

    ArgumentChecker.isTrue(
        xValues.length == yValues.length | xValues.length + 2 == yValues.length,
        "(xValues length = yValues length) or (xValues length + 2 = yValues length)");
    ArgumentChecker.isTrue(xValues.length > 2, "Data points should be more than 2");

    final int nDataPts = xValues.length;
    final int yValuesLen = yValues.length;

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

    for (int i = 0; i < nDataPts - 1; ++i) {
      for (int j = i + 1; j < nDataPts; ++j) {
        ArgumentChecker.isFalse(xValues[i] == xValues[j], "xValues should be distinct");
      }
    }

    double[] xValuesSrt = Arrays.copyOf(xValues, nDataPts);
    double[] yValuesSrt = new double[nDataPts];
    if (nDataPts == yValuesLen) {
      yValuesSrt = Arrays.copyOf(yValues, nDataPts);
    } else {
      yValuesSrt = Arrays.copyOfRange(yValues, 1, nDataPts + 1);
    }
    ParallelArrayBinarySort.parallelBinarySort(xValuesSrt, yValuesSrt);

    final double[] intervals = _solver.intervalsCalculator(xValuesSrt);
    final double[] slopes = _solver.slopesCalculator(yValuesSrt, intervals);
    final PiecewisePolynomialResult result = _method.interpolate(xValues, yValues);

    ArgumentChecker.isTrue(result.getOrder() >= 3, "Primary interpolant should be degree >= 2");

    final double[] initialFirst = _function.differentiate(result, xValuesSrt).getData()[0];
    final double[] initialSecond = _function.differentiateTwice(result, xValuesSrt).getData()[0];
    double[] first = firstDerivativeCalculator(yValuesSrt, intervals, slopes, initialFirst);

    boolean modFirst = false;
    int k;
    double[] aValues = aValuesCalculator(slopes, first);
    double[] bValues = bValuesCalculator(slopes, first);
    double[][] intervalsA = getIntervalsA(intervals, slopes, first, bValues);
    double[][] intervalsB = getIntervalsB(intervals, slopes, first, aValues);
    while (modFirst == false) {
      k = 0;
      for (int i = 0; i < nDataPts - 2; ++i) {
        if (first[i + 1] > 0.) {
          if (intervalsA[i + 1][1] + Math.abs(intervalsA[i + 1][1]) * ERROR
                  < intervalsB[i][0] - Math.abs(intervalsB[i][0]) * ERROR
              | intervalsA[i + 1][0] - Math.abs(intervalsA[i + 1][0]) * ERROR
                  > intervalsB[i][1] + Math.abs(intervalsB[i][1]) * ERROR) {
            ++k;
            first[i + 1] = firstDerivativesRecalculator(intervals, slopes, aValues, bValues, i + 1);
          }
        }
      }
      if (k == 0) {
        modFirst = true;
      }
      aValues = aValuesCalculator(slopes, first);
      bValues = bValuesCalculator(slopes, first);
      intervalsA = getIntervalsA(intervals, slopes, first, bValues);
      intervalsB = getIntervalsB(intervals, slopes, first, aValues);
    }
    final double[] second = secondDerivativeCalculator(initialSecond, intervalsA, intervalsB);
    final double[][] coefs = _solver.solve(yValuesSrt, intervals, slopes, first, second);

    for (int i = 0; i < nDataPts - 1; ++i) {
      for (int j = 0; j < 6; ++j) {
        ArgumentChecker.isFalse(Double.isNaN(coefs[i][j]), "Too large input");
        ArgumentChecker.isFalse(Double.isInfinite(coefs[i][j]), "Too large input");
      }
    }

    return new PiecewisePolynomialResult(
        new DoubleMatrix1D(xValuesSrt), new DoubleMatrix2D(coefs), 6, 1);
  }
  @Override
  public PiecewisePolynomialResultsWithSensitivity interpolateWithSensitivity(
      final double[] xValues, final double[] yValues) {

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

    ArgumentChecker.isTrue(
        xValues.length == yValues.length | xValues.length + 2 == yValues.length,
        "(xValues length = yValues length) or (xValues length + 2 = yValues length)");
    ArgumentChecker.isTrue(xValues.length > 2, "Data points should be more than 2");

    final int nDataPts = xValues.length;
    final int yValuesLen = yValues.length;

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

    for (int i = 0; i < nDataPts - 1; ++i) {
      for (int j = i + 1; j < nDataPts; ++j) {
        ArgumentChecker.isFalse(xValues[i] == xValues[j], "xValues should be distinct");
      }
    }

    double[] yValuesSrt = new double[nDataPts];
    if (nDataPts == yValuesLen) {
      yValuesSrt = Arrays.copyOf(yValues, nDataPts);
    } else {
      yValuesSrt = Arrays.copyOfRange(yValues, 1, nDataPts + 1);
    }

    final double[] intervals = _solver.intervalsCalculator(xValues);
    final double[] slopes = _solver.slopesCalculator(yValuesSrt, intervals);
    double[][] slopesSensitivity = _solver.slopeSensitivityCalculator(intervals);
    final DoubleMatrix1D[] firstWithSensitivity = new DoubleMatrix1D[nDataPts + 1];
    final DoubleMatrix1D[] secondWithSensitivity = new DoubleMatrix1D[nDataPts + 1];
    final PiecewisePolynomialResult result = _method.interpolate(xValues, yValues);

    ArgumentChecker.isTrue(result.getOrder() >= 3, "Primary interpolant should be degree >= 2");

    final double[] initialFirst = _function.differentiate(result, xValues).getData()[0];
    final double[] initialSecond = _function.differentiateTwice(result, xValues).getData()[0];
    double[] first = firstDerivativeCalculator(yValuesSrt, intervals, slopes, initialFirst);
    boolean modFirst = false;
    int k;
    double[] aValues = aValuesCalculator(slopes, first);
    double[] bValues = bValuesCalculator(slopes, first);
    double[][] intervalsA = getIntervalsA(intervals, slopes, first, bValues);
    double[][] intervalsB = getIntervalsB(intervals, slopes, first, aValues);
    while (modFirst == false) {
      k = 0;
      for (int i = 0; i < nDataPts - 2; ++i) {
        if (first[i + 1] > 0.) {
          if (intervalsA[i + 1][1] + Math.abs(intervalsA[i + 1][1]) * ERROR
                  < intervalsB[i][0] - Math.abs(intervalsB[i][0]) * ERROR
              | intervalsA[i + 1][0] - Math.abs(intervalsA[i + 1][0]) * ERROR
                  > intervalsB[i][1] + Math.abs(intervalsB[i][1]) * ERROR) {
            ++k;
            first[i + 1] = firstDerivativesRecalculator(intervals, slopes, aValues, bValues, i + 1);
          }
        }
      }
      if (k == 0) {
        modFirst = true;
      }
      aValues = aValuesCalculator(slopes, first);
      bValues = bValuesCalculator(slopes, first);
      intervalsA = getIntervalsA(intervals, slopes, first, bValues);
      intervalsB = getIntervalsB(intervals, slopes, first, aValues);
    }
    final double[] second = secondDerivativeCalculator(initialSecond, intervalsA, intervalsB);
    firstWithSensitivity[0] = new DoubleMatrix1D(first);
    secondWithSensitivity[0] = new DoubleMatrix1D(second);

    /*
     * Centered finite difference method is used for computing node sensitivity
     */
    int nExtra = (nDataPts == yValuesLen) ? 0 : 1;
    final double[] yValuesUp = Arrays.copyOf(yValues, nDataPts + 2 * nExtra);
    final double[] yValuesDw = Arrays.copyOf(yValues, nDataPts + 2 * nExtra);
    final double[][] tmpFirst = new double[nDataPts][nDataPts];
    final double[][] tmpSecond = new double[nDataPts][nDataPts];
    for (int l = nExtra; l < nDataPts + nExtra; ++l) {
      final double den = Math.abs(yValues[l]) < SMALL ? EPS : yValues[l] * EPS;
      yValuesUp[l] = Math.abs(yValues[l]) < SMALL ? EPS : yValues[l] * (1. + EPS);
      yValuesDw[l] = Math.abs(yValues[l]) < SMALL ? -EPS : yValues[l] * (1. - EPS);
      final double[] yValuesSrtUp = Arrays.copyOfRange(yValuesUp, nExtra, nDataPts + nExtra);
      final double[] yValuesSrtDw = Arrays.copyOfRange(yValuesDw, nExtra, nDataPts + nExtra);

      final DoubleMatrix1D[] yValuesUpDw =
          new DoubleMatrix1D[] {new DoubleMatrix1D(yValuesUp), new DoubleMatrix1D(yValuesDw)};
      final DoubleMatrix1D[] yValuesSrtUpDw =
          new DoubleMatrix1D[] {new DoubleMatrix1D(yValuesSrtUp), new DoubleMatrix1D(yValuesSrtDw)};
      final DoubleMatrix1D[] firstSecondUpDw = new DoubleMatrix1D[4];
      for (int ii = 0; ii < 2; ++ii) {
        final double[] slopesUpDw =
            _solver.slopesCalculator(yValuesSrtUpDw[ii].getData(), intervals);
        final PiecewisePolynomialResult resultUpDw =
            _method.interpolate(xValues, yValuesUpDw[ii].getData());
        final double[] initialFirstUpDw = _function.differentiate(resultUpDw, xValues).getData()[0];
        final double[] initialSecondUpDw =
            _function.differentiateTwice(resultUpDw, xValues).getData()[0];
        double[] firstUpDw =
            firstDerivativeCalculator(
                yValuesSrtUpDw[ii].getData(), intervals, slopesUpDw, initialFirstUpDw);
        boolean modFirstUpDw = false;
        double[] aValuesUpDw = aValuesCalculator(slopesUpDw, firstUpDw);
        double[] bValuesUpDw = bValuesCalculator(slopesUpDw, firstUpDw);
        double[][] intervalsAUpDw = getIntervalsA(intervals, slopesUpDw, firstUpDw, bValuesUpDw);
        double[][] intervalsBUpDw = getIntervalsB(intervals, slopesUpDw, firstUpDw, aValuesUpDw);
        while (modFirstUpDw == false) {
          k = 0;
          for (int i = 0; i < nDataPts - 2; ++i) {
            if (firstUpDw[i + 1] > 0.) {
              if (intervalsAUpDw[i + 1][1] + Math.abs(intervalsAUpDw[i + 1][1]) * ERROR
                      < intervalsBUpDw[i][0] - Math.abs(intervalsBUpDw[i][0]) * ERROR
                  | intervalsAUpDw[i + 1][0] - Math.abs(intervalsAUpDw[i + 1][0]) * ERROR
                      > intervalsBUpDw[i][1] + Math.abs(intervalsBUpDw[i][1]) * ERROR) {
                ++k;
                firstUpDw[i + 1] =
                    firstDerivativesRecalculator(
                        intervals, slopesUpDw, aValuesUpDw, bValuesUpDw, i + 1);
              }
            }
          }
          if (k == 0) {
            modFirstUpDw = true;
          }
          aValuesUpDw = aValuesCalculator(slopesUpDw, firstUpDw);
          bValuesUpDw = bValuesCalculator(slopesUpDw, firstUpDw);
          intervalsAUpDw = getIntervalsA(intervals, slopesUpDw, firstUpDw, bValuesUpDw);
          intervalsBUpDw = getIntervalsB(intervals, slopesUpDw, firstUpDw, aValuesUpDw);
        }
        final double[] secondUpDw =
            secondDerivativeCalculator(initialSecondUpDw, intervalsAUpDw, intervalsBUpDw);
        firstSecondUpDw[ii] = new DoubleMatrix1D(firstUpDw);
        firstSecondUpDw[2 + ii] = new DoubleMatrix1D(secondUpDw);
      }
      for (int j = 0; j < nDataPts; ++j) {
        tmpFirst[j][l - nExtra] =
            0.5 * (firstSecondUpDw[0].getData()[j] - firstSecondUpDw[1].getData()[j]) / den;
        tmpSecond[j][l - nExtra] =
            0.5 * (firstSecondUpDw[2].getData()[j] - firstSecondUpDw[3].getData()[j]) / den;
      }
      yValuesUp[l] = yValues[l];
      yValuesDw[l] = yValues[l];
    }
    for (int i = 0; i < nDataPts; ++i) {
      firstWithSensitivity[i + 1] = new DoubleMatrix1D(tmpFirst[i]);
      secondWithSensitivity[i + 1] = new DoubleMatrix1D(tmpSecond[i]);
    }

    final DoubleMatrix2D[] resMatrix =
        _solver.solveWithSensitivity(
            yValuesSrt,
            intervals,
            slopes,
            slopesSensitivity,
            firstWithSensitivity,
            secondWithSensitivity);

    for (int l = 0; l < nDataPts; ++l) {
      DoubleMatrix2D m = resMatrix[l];
      final int rows = m.getNumberOfRows();
      final int cols = m.getNumberOfColumns();
      for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
          ArgumentChecker.isTrue(
              Doubles.isFinite(m.getEntry(i, j)), "Matrix contains a NaN or infinite");
        }
      }
    }

    final DoubleMatrix2D coefMatrix = resMatrix[0];
    final DoubleMatrix2D[] coefSenseMatrix = new DoubleMatrix2D[nDataPts - 1];
    System.arraycopy(resMatrix, 1, coefSenseMatrix, 0, nDataPts - 1);
    final int nCoefs = coefMatrix.getNumberOfColumns();

    return new PiecewisePolynomialResultsWithSensitivity(
        new DoubleMatrix1D(xValues), coefMatrix, nCoefs, 1, coefSenseMatrix);
  }