public void test_volatility_sensitivity() {
   double eps = 1.0e-6;
   int nData = TIME.size();
   for (int i = 0; i < NB_TEST; i++) {
     SwaptionSensitivity point =
         SwaptionSensitivity.of(
             CONVENTION,
             TEST_OPTION_EXPIRY[i],
             TENOR.get(i),
             TEST_STRIKE,
             TEST_FORWARD,
             GBP,
             TEST_SENSITIVITY[i]);
     SurfaceCurrencyParameterSensitivity sensi =
         PROVIDER_WITH_PARAM.surfaceCurrencyParameterSensitivity(point);
     Map<DoublesPair, Double> map = new HashMap<DoublesPair, Double>();
     for (int j = 0; j < nData; ++j) {
       DoubleArray volDataUp = VOL.subArray(0, nData).with(j, VOL.get(j) + eps);
       DoubleArray volDataDw = VOL.subArray(0, nData).with(j, VOL.get(j) - eps);
       InterpolatedNodalSurface paramUp =
           InterpolatedNodalSurface.of(
               METADATA_WITH_PARAM, TIME, TENOR, volDataUp, INTERPOLATOR_2D);
       InterpolatedNodalSurface paramDw =
           InterpolatedNodalSurface.of(
               METADATA_WITH_PARAM, TIME, TENOR, volDataDw, INTERPOLATOR_2D);
       BlackVolatilityExpiryTenorSwaptionProvider provUp =
           BlackVolatilityExpiryTenorSwaptionProvider.of(
               paramUp, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
       BlackVolatilityExpiryTenorSwaptionProvider provDw =
           BlackVolatilityExpiryTenorSwaptionProvider.of(
               paramDw, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
       double volUp =
           provUp.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
       double volDw =
           provDw.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
       double fd = 0.5 * (volUp - volDw) / eps;
       map.put(DoublesPair.of(TIME.get(j), TENOR.get(j)), fd);
     }
     SurfaceCurrencyParameterSensitivity sensiFromNoMetadata =
         PROVIDER.surfaceCurrencyParameterSensitivity(point);
     List<SurfaceParameterMetadata> list = sensi.getMetadata().getParameterMetadata().get();
     DoubleArray computed = sensi.getSensitivity();
     assertEquals(computed.size(), nData);
     for (int j = 0; j < list.size(); ++j) {
       SwaptionSurfaceExpiryTenorNodeMetadata metadata =
           (SwaptionSurfaceExpiryTenorNodeMetadata) list.get(i);
       double expected = map.get(DoublesPair.of(metadata.getYearFraction(), metadata.getTenor()));
       assertEquals(computed.get(i), expected, eps);
       assertTrue(
           sensiFromNoMetadata.getMetadata().getParameterMetadata().get().contains(metadata));
     }
   }
 }
 @Override
 public SurfaceCurrencyParameterSensitivity surfaceCurrencyParameterSensitivity(
     IborCapletFloorletSensitivity point) {
   ArgChecker.isTrue(
       point.getIndex().equals(index),
       "Ibor index of provider must be the same as Ibor index of point sensitivity");
   double expiry = relativeTime(point.getExpiry());
   double strike = point.getStrike();
   // copy to ImmutableMap to lock order (keySet and values used separately but must match)
   Map<DoublesPair, Double> result =
       ImmutableMap.copyOf(surface.zValueParameterSensitivity(expiry, strike));
   SurfaceCurrencyParameterSensitivity parameterSensi =
       SurfaceCurrencyParameterSensitivity.of(
           updateSurfaceMetadata(result.keySet()),
           point.getCurrency(),
           DoubleArray.copyOf(Doubles.toArray(result.values())));
   return parameterSensi.multipliedBy(point.getSensitivity());
 }
 private void testPriceSensitivityBlackVolatility(
     SurfaceCurrencyParameterSensitivity computed,
     Function<BlackVolatilityBondFutureProvider, Double> valueFn) {
   List<SurfaceParameterMetadata> list = computed.getMetadata().getParameterMetadata().get();
   int nVol = VOL.size();
   assertEquals(list.size(), nVol);
   for (int i = 0; i < nVol; ++i) {
     double[] volUp = Arrays.copyOf(VOL.toArray(), nVol);
     double[] volDw = Arrays.copyOf(VOL.toArray(), nVol);
     volUp[i] += EPS;
     volDw[i] -= EPS;
     InterpolatedNodalSurface sfUp =
         InterpolatedNodalSurface.of(
             METADATA, TIME, MONEYNESS, DoubleArray.copyOf(volUp), INTERPOLATOR_2D);
     InterpolatedNodalSurface sfDw =
         InterpolatedNodalSurface.of(
             METADATA, TIME, MONEYNESS, DoubleArray.copyOf(volDw), INTERPOLATOR_2D);
     BlackVolatilityExpLogMoneynessBondFutureProvider provUp =
         BlackVolatilityExpLogMoneynessBondFutureProvider.of(
             sfUp, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
     BlackVolatilityExpLogMoneynessBondFutureProvider provDw =
         BlackVolatilityExpLogMoneynessBondFutureProvider.of(
             sfDw, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
     double expected = 0.5 * (valueFn.apply(provUp) - valueFn.apply(provDw)) / EPS;
     int index = -1;
     for (int j = 0; j < nVol; ++j) {
       GenericVolatilitySurfaceYearFractionMetadata meta =
           (GenericVolatilitySurfaceYearFractionMetadata) list.get(j);
       if (meta.getYearFraction() == TIME.get(i)
           && meta.getStrike().getValue() == MONEYNESS.get(i)) {
         index = j;
         continue;
       }
     }
     assertEquals(computed.getSensitivity().get(index), expected, EPS);
   }
 }