@Test
  public void sensitivityTest() {
    final VolatilitySurfaceProvider vsPro =
        new BasisSplineVolatilitySurfaceProvider(0.0, 0.15, 10, 3, 0.0, 10.0, 7, 2);

    final DoubleMatrix1D w = new DoubleMatrix1D(vsPro.getNumModelParameters());
    for (int i = 0; i < w.getNumberOfElements(); i++) {
      w.getData()[i] = RANDOM.nextDouble();
    }

    final VolatilitySurface volSurf = vsPro.getVolSurface(w);
    final Surface<Double, Double, DoubleMatrix1D> senseSurf =
        vsPro.getParameterSensitivitySurface(w);
    final Surface<Double, Double, Pair<Double, DoubleMatrix1D>> volAndSenseSurf =
        vsPro.getVolAndParameterSensitivitySurface(w);

    final int nSamples = 20;
    final DoublesPair[] points = new DoublesPair[nSamples];
    for (int i = 0; i < nSamples; i++) {
      final double t = 10.0 * RANDOM.nextDouble();
      final double k = 0.15 * RANDOM.nextDouble();
      points[i] = DoublesPair.of(t, k);

      final double vol = volSurf.getVolatility(points[i]);
      final DoubleMatrix1D sense = senseSurf.getZValue(points[i].toPair());
      final Pair<Double, DoubleMatrix1D> volAndSense =
          volAndSenseSurf.getZValue(points[i].toPair());
      assertEquals(vol, volAndSense.getFirst(), 1e-15);
      AssertMatrix.assertEqualsVectors(sense, volAndSense.getSecond(), 1e-15);
    }

    // create a DiscreteVolatilityFunctionProvider in order to compute the Jacobian for a (random)
    // set the points
    final DiscreteVolatilityFunctionProvider dvfp =
        new DiscreteVolatilityFunctionProviderFromVolSurface(vsPro);
    final DiscreteVolatilityFunction func = dvfp.from(points);
    final DoubleMatrix2D jac = func.calculateJacobian(w);
    final DoubleMatrix2D jacFD = func.calculateJacobianViaFD(w);
    AssertMatrix.assertEqualsMatrix(jacFD, jac, 1e-10);
  }
  public void partitionByValue() {
    List<LocalDate> dates =
        dates(DATE_2010_01_01, DATE_2011_06_01, DATE_2012_01_01, DATE_2013_06_01, DATE_2014_01_01);
    LocalDateDoubleTimeSeries series =
        LocalDateDoubleTimeSeries.builder().putAll(dates, VALUES_10_14).build();

    Pair<LocalDateDoubleTimeSeries, LocalDateDoubleTimeSeries> partition =
        series.partitionByValue(d -> d > 10 && d < 14);

    LocalDateDoubleTimeSeries mid = partition.getFirst();
    LocalDateDoubleTimeSeries extreme = partition.getSecond();

    assertThat(mid.size()).isEqualTo(3);
    assertThat(extreme.size()).isEqualTo(2);

    assertThat(mid.get(DATE_2011_06_01)).hasValue(11);
    assertThat(mid.get(DATE_2012_01_01)).hasValue(12);
    assertThat(mid.get(DATE_2013_06_01)).hasValue(13);

    assertThat(extreme.get(DATE_2010_01_01)).hasValue(10);
    assertThat(extreme.get(DATE_2014_01_01)).hasValue(14);
  }
  public void partition() {
    List<LocalDate> dates =
        dates(DATE_2010_01_01, DATE_2011_06_01, DATE_2012_01_01, DATE_2013_06_01, DATE_2014_01_01);
    LocalDateDoubleTimeSeries series =
        LocalDateDoubleTimeSeries.builder().putAll(dates, VALUES_10_14).build();

    Pair<LocalDateDoubleTimeSeries, LocalDateDoubleTimeSeries> partition =
        series.partition((ld, d) -> ld.getYear() % 2 == 0);

    LocalDateDoubleTimeSeries even = partition.getFirst();
    LocalDateDoubleTimeSeries odd = partition.getSecond();

    assertThat(even.size()).isEqualTo(3);
    assertThat(odd.size()).isEqualTo(2);

    assertThat(even.get(DATE_2010_01_01)).hasValue(10);
    assertThat(even.get(DATE_2012_01_01)).hasValue(12);
    assertThat(even.get(DATE_2014_01_01)).hasValue(14);

    assertThat(odd.get(DATE_2011_06_01)).hasValue(11);
    assertThat(odd.get(DATE_2013_06_01)).hasValue(13);
  }
  @Test(enabled = true)
  public void swapRateDx2Ddcf() {
    final double theta = 1.99;
    final double rhog2pp = MODEL_PARAMETERS.getCorrelation();
    final double[][] gamma = MODEL_G2PP.gamma(MODEL_PARAMETERS, 0, theta);
    double[][] alphaFixed = new double[T_FIXED.length][2];
    double[] tau2Fixed = new double[T_FIXED.length];
    final double[][] alphaIbor = new double[T_IBOR.length][2];
    final double[] tau2Ibor = new double[T_IBOR.length];
    final double[][] hthetaFixed =
        MODEL_G2PP.volatilityMaturityPart(MODEL_PARAMETERS, theta, T_FIXED);
    alphaFixed = new double[2][T_FIXED.length];
    tau2Fixed = new double[T_FIXED.length];
    for (int loopcf = 0; loopcf < T_FIXED.length; loopcf++) {
      alphaFixed[loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaFixed[0][loopcf];
      alphaFixed[loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaFixed[1][loopcf];
      tau2Fixed[loopcf] =
          alphaFixed[loopcf][0] * alphaFixed[loopcf][0]
              + alphaFixed[loopcf][1] * alphaFixed[loopcf][1]
              + 2 * rhog2pp * gamma[0][1] * hthetaFixed[0][loopcf] * hthetaFixed[1][loopcf];
    }
    final double[][] hthetaIbor =
        MODEL_G2PP.volatilityMaturityPart(MODEL_PARAMETERS, theta, T_IBOR);
    for (int loopcf = 0; loopcf < T_IBOR.length; loopcf++) {
      alphaIbor[loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaIbor[0][loopcf];
      alphaIbor[loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaIbor[1][loopcf];
      tau2Ibor[loopcf] =
          alphaIbor[loopcf][0] * alphaIbor[loopcf][0]
              + alphaIbor[loopcf][1] * alphaIbor[loopcf][1]
              + 2 * rhog2pp * gamma[0][1] * hthetaIbor[0][loopcf] * hthetaIbor[1][loopcf];
    }

    final double shift = 1.0E-7;
    final double[] x = {0.0, 0.1};
    final Pair<double[][][], double[][][]> dx2ddcfComputed =
        MODEL_G2PP.swapRateDdcfDx2(
            x, DCF_FIXED, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor);
    final double[][][] dx2DdcffExpected = new double[DCF_FIXED.length][2][2];
    for (int loopcf = 0; loopcf < DCF_FIXED.length; loopcf++) {
      final double[] dsf_bumped = DCF_FIXED.clone();
      dsf_bumped[loopcf] += shift;
      final double[] d1Plus = new double[2];
      final double[][] d2Plus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, dsf_bumped, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor, d1Plus, d2Plus);
      dsf_bumped[loopcf] -= 2 * shift;
      final double[] d1Minus = new double[2];
      final double[][] d2Minus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, dsf_bumped, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor, d1Minus, d2Minus);
      for (int loopd1 = 0; loopd1 < 2; loopd1++) {
        for (int loopd2 = loopd1; loopd2 < 2; loopd2++) {
          dx2DdcffExpected[loopcf][loopd1][loopd2] =
              (d2Plus[loopd1][loopd2] - d2Minus[loopd1][loopd2]) / (2 * shift);
          assertEquals(
              "Hull-White model: swap rate",
              dx2DdcffExpected[loopcf][loopd1][loopd2],
              dx2ddcfComputed.getFirst()[loopcf][loopd1][loopd2],
              TOLERANCE_RATE_DELTA2);
        }
      }
    }
    final double[][][] dx2DdcfiExpected = new double[DCF_IBOR.length][2][2];
    for (int loopcf = 0; loopcf < DCF_IBOR.length; loopcf++) {
      final double[] dsf_bumped = DCF_IBOR.clone();
      dsf_bumped[loopcf] += shift;
      final double[] d1Plus = new double[2];
      final double[][] d2Plus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, DCF_FIXED, alphaFixed, tau2Fixed, dsf_bumped, alphaIbor, tau2Ibor, d1Plus, d2Plus);
      dsf_bumped[loopcf] -= 2 * shift;
      final double[] d1Minus = new double[2];
      final double[][] d2Minus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, DCF_FIXED, alphaFixed, tau2Fixed, dsf_bumped, alphaIbor, tau2Ibor, d1Minus, d2Minus);
      for (int loopd1 = 0; loopd1 < 2; loopd1++) {
        for (int loopd2 = loopd1; loopd2 < 2; loopd2++) {
          dx2DdcfiExpected[loopcf][loopd1][loopd2] =
              (d2Plus[loopd1][loopd2] - d2Minus[loopd1][loopd2]) / (2 * shift);
          assertEquals(
              "Hull-White model: swap rate",
              dx2DdcfiExpected[loopcf][loopd1][loopd2],
              dx2ddcfComputed.getSecond()[loopcf][loopd1][loopd2],
              TOLERANCE_RATE_DELTA2);
        }
      }
    }
  }