private void testSurfaceSensitivity(CurrencyParameterSensitivity computed, NormalIborCapletFloorletExpiryStrikeVolatilities vols, System.Func <IborCapletFloorletVolatilities, CurrencyAmount> valueFn)
        {
            double pvBase = valueFn(vols).Amount;
            InterpolatedNodalSurface surfaceBase = (InterpolatedNodalSurface)vols.Surface;
            int nParams = surfaceBase.ParameterCount;

            for (int i = 0; i < nParams; ++i)
            {
                DoubleArray zBumped = surfaceBase.ZValues.with(i, surfaceBase.ZValues.get(i) + EPS_FD);
                InterpolatedNodalSurface surfaceBumped = surfaceBase.withZValues(zBumped);
                NormalIborCapletFloorletExpiryStrikeVolatilities volsBumped = NormalIborCapletFloorletExpiryStrikeVolatilities.of(vols.Index, vols.ValuationDateTime, surfaceBumped);
                double fd = (valueFn(volsBumped).Amount - pvBase) / EPS_FD;
                assertEquals(computed.Sensitivity.get(i), fd, NOTIONAL * EPS_FD);
            }
        }
        /// <summary>
        /// Returns the swaption normal volatility surface shifted by a given amount. The shift is parallel. </summary>
        /// <param name="shift">  the shift </param>
        /// <returns> the swaption normal volatility surface </returns>
        public static NormalSwaptionExpiryTenorVolatilities normalVolSwaptionProviderUsdStsShifted(double shift)
        {
            DoubleArray volShifted = NORMAL_VOL.map(v => v + shift);

            return(NormalSwaptionExpiryTenorVolatilities.of(USD_1Y_LIBOR3M, VAL_DATE_TIME_STD, SURFACE_STD.withZValues(volShifted)));
        }