/**
   * Computes the present value sensitivity to the strike of a CMS cap/floor by replication in SABR
   * framework with extrapolation on the right.
   *
   * @param cmsCapFloor The CMS cap/floor.
   * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function.
   * @return The present value sensitivity to strike.
   */
  @Override
  public double presentValueStrikeSensitivity(
      final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
    final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
    final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
    final double forward = underlyingSwap.accept(PRC, sabrData);
    final double discountFactor =
        sabrData
            .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
            .getDiscountFactor(cmsCapFloor.getPaymentTime());
    final double strike = cmsCapFloor.getStrike();
    final double maturity =
        underlyingSwap
                .getFixedLeg()
                .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - cmsCapFloor.getSettlementTime();
    final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
    final double alpha = sabrParameter.getAlpha(expiryMaturity);
    final double beta = sabrParameter.getBeta(expiryMaturity);
    final double rho = sabrParameter.getRho(expiryMaturity);
    final double nu = sabrParameter.getNu(expiryMaturity);
    final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
    final CMSStrikeIntegrant integrant =
        new CMSStrikeIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
    final double factor = discountFactor * integrant.g(forward) / integrant.h(forward);
    final double absoluteTolerance = 1.0E-9;
    final double relativeTolerance = 1.0E-5;
    final RungeKuttaIntegrator1D integrator =
        new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
    final SABRExtrapolationRightFunction sabrExtrapolation =
        new SABRExtrapolationRightFunction(
            forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu);
    final EuropeanVanillaOption option =
        new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap());
    final double[] kpkpp = integrant.kpkpp(strike);
    double firstPart;
    double thirdPart;
    if (cmsCapFloor.isCap()) {
      firstPart = -kpkpp[0] * integrant.bs(strike);
      thirdPart = integrator.integrate(integrant, strike, strike + getIntegrationInterval());
    } else {
      firstPart = 3 * kpkpp[0] * integrant.bs(strike);
      thirdPart = integrator.integrate(integrant, 0.0, strike);
    }
    final double secondPart = integrant.k(strike) * sabrExtrapolation.priceDerivativeStrike(option);

    return cmsCapFloor.getNotional()
        * cmsCapFloor.getPaymentYearFraction()
        * factor
        * (firstPart + secondPart + thirdPart);
  }
 /**
  * Compute the present value of a CMS cap/floor by replication in SABR framework with
  * extrapolation on the right.
  *
  * @param cmsCapFloor The CMS cap/floor.
  * @param sabrData The SABR data bundle.
  * @return The present value.
  */
 @Override
 public CurrencyAmount presentValue(
     final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
   Validate.notNull(cmsCapFloor);
   Validate.notNull(sabrData);
   final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
   final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
   final double forward = underlyingSwap.accept(PRC, sabrData);
   final double discountFactorTp =
       sabrData
           .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
           .getDiscountFactor(cmsCapFloor.getPaymentTime());
   final double maturity =
       underlyingSwap
               .getFixedLeg()
               .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
               .getPaymentTime()
           - cmsCapFloor.getSettlementTime();
   final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
   final double alpha = sabrParameter.getAlpha(expiryMaturity);
   final double beta = sabrParameter.getBeta(expiryMaturity);
   final double rho = sabrParameter.getRho(expiryMaturity);
   final double nu = sabrParameter.getNu(expiryMaturity);
   final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
   final CMSIntegrant integrant =
       new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
   final double strike = cmsCapFloor.getStrike();
   final double factor = discountFactorTp / integrant.h(forward) * integrant.g(forward);
   final double strikePart = factor * integrant.k(strike) * integrant.bs(strike);
   final double absoluteTolerance =
       1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
   final double relativeTolerance = 1E-10;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
   double integralPart;
   try {
     if (cmsCapFloor.isCap()) {
       integralPart =
           discountFactorTp
               * integrator.integrate(integrant, strike, strike + getIntegrationInterval());
     } else {
       integralPart = discountFactorTp * integrator.integrate(integrant, 0.0, strike);
     }
   } catch (final Exception e) {
     throw new RuntimeException(e);
   }
   final double priceCMS =
       (strikePart + integralPart)
           * cmsCapFloor.getNotional()
           * cmsCapFloor.getPaymentYearFraction();
   return CurrencyAmount.of(cmsCapFloor.getCurrency(), priceCMS);
 }
 /**
  * Computes the present value of the Physical delivery swaption.
  *
  * @param swaption The swaption.
  * @param hwData The Hull-White parameters and the curves.
  * @return The present value.
  */
 public CurrencyAmount presentValue(
     final SwaptionPhysicalFixedIbor swaption,
     final HullWhiteOneFactorPiecewiseConstantDataBundle hwData) {
   Validate.notNull(swaption);
   Validate.notNull(hwData);
   final double expiryTime = swaption.getTimeToExpiry();
   final AnnuityPaymentFixed cfe = swaption.getUnderlyingSwap().accept(CFEC, hwData);
   final double[] alpha = new double[cfe.getNumberOfPayments()];
   final double[] df = new double[cfe.getNumberOfPayments()];
   final double[] discountedCashFlow = new double[cfe.getNumberOfPayments()];
   for (int loopcf = 0; loopcf < cfe.getNumberOfPayments(); loopcf++) {
     alpha[loopcf] =
         MODEL.alpha(
             hwData.getHullWhiteParameter(),
             0.0,
             expiryTime,
             expiryTime,
             cfe.getNthPayment(loopcf).getPaymentTime());
     df[loopcf] =
         hwData
             .getCurve(cfe.getDiscountCurve())
             .getDiscountFactor(cfe.getNthPayment(loopcf).getPaymentTime());
     discountedCashFlow[loopcf] = df[loopcf] * cfe.getNthPayment(loopcf).getAmount();
   }
   // Integration
   final SwaptionIntegrant integrant = new SwaptionIntegrant(discountedCashFlow, alpha);
   final double limit = 10.0;
   final double absoluteTolerance = 1.0E-2;
   final double relativeTolerance = 1.0E-6;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, NB_INTEGRATION);
   double pv = 0.0;
   try {
     pv =
         1.0
             / Math.sqrt(2.0 * Math.PI)
             * integrator.integrate(integrant, -limit, limit)
             * (swaption.isLong() ? 1.0 : -1.0);
   } catch (final Exception e) {
     throw new RuntimeException(e);
   }
   return CurrencyAmount.of(swaption.getCurrency(), pv);
 }
 /**
  * Computes the future price from the curves used to price the underlying bonds and a Hull-White
  * one factor model. Computation by numerical integration.
  *
  * @param futures The future security.
  * @param data The curve and Hull-White parameters.
  * @return The future price.
  */
 public double price(
     final BondFuturesSecurity futures, final HullWhiteIssuerProviderInterface data) {
   ArgumentChecker.notNull(futures, "Futures");
   ArgumentChecker.notNull(data, "Hull-White/Issuer provider");
   final Currency ccy = futures.getCurrency();
   final LegalEntity issuer = futures.getDeliveryBasketAtDeliveryDate()[0].getIssuerEntity();
   final double expiryTime = futures.getNoticeLastTime();
   final double deliveryTime = futures.getDeliveryLastTime();
   final int nbBonds = futures.getDeliveryBasketAtDeliveryDate().length;
   final int[] nbPayments = new int[nbBonds];
   final AnnuityPaymentFixed[] cfe = new AnnuityPaymentFixed[nbBonds];
   for (int loopb = 0; loopb < nbBonds; loopb++) {
     cfe[loopb] =
         futures.getDeliveryBasketAtDeliveryDate()[loopb].accept(
             CFEC, data.getMulticurveProvider());
     nbPayments[loopb] = cfe[loopb].getNumberOfPayments();
     final PaymentFixed[] payments = new PaymentFixed[nbPayments[loopb] + 1];
     payments[0] =
         new PaymentFixed(
             ccy,
             deliveryTime,
             -futures.getDeliveryBasketAtDeliveryDate()[loopb].getAccruedInterest());
     System.arraycopy(cfe[loopb].getPayments(), 0, payments, 1, nbPayments[loopb]);
     cfe[loopb] = new AnnuityPaymentFixed(payments);
   }
   final double[][] alpha = new double[nbBonds][];
   final double[][] beta = new double[nbBonds][];
   final double[][] df = new double[nbBonds][];
   final double[][] discountedCashFlow = new double[nbBonds][];
   for (int loopb = 0; loopb < nbBonds; loopb++) {
     alpha[loopb] = new double[nbPayments[loopb] + 1];
     beta[loopb] = new double[nbPayments[loopb] + 1];
     df[loopb] = new double[nbPayments[loopb] + 1];
     discountedCashFlow[loopb] = new double[nbPayments[loopb] + 1];
     for (int loopcf = 0; loopcf < cfe[loopb].getNumberOfPayments(); loopcf++) {
       alpha[loopb][loopcf] =
           MODEL.alpha(
               data.getHullWhiteParameters(),
               0.0,
               expiryTime,
               deliveryTime,
               cfe[loopb].getNthPayment(loopcf).getPaymentTime());
       beta[loopb][loopcf] =
           MODEL.futuresConvexityFactor(
               data.getHullWhiteParameters(),
               expiryTime,
               cfe[loopb].getNthPayment(loopcf).getPaymentTime(),
               deliveryTime);
       df[loopb][loopcf] =
           data.getIssuerProvider()
               .getDiscountFactor(issuer, cfe[loopb].getNthPayment(loopcf).getPaymentTime());
       discountedCashFlow[loopb][loopcf] =
           df[loopb][loopcf]
               / df[loopb][0]
               * cfe[loopb].getNthPayment(loopcf).getAmount()
               * beta[loopb][loopcf]
               / futures.getConversionFactor()[loopb];
     }
   }
   // Integration
   final FuturesIntegrant integrant = new FuturesIntegrant(discountedCashFlow, alpha);
   final double limit = 10.0;
   final double absoluteTolerance = 1.0E-2;
   final double relativeTolerance = 1.0E-6;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, NB_INTEGRATION);
   double price = 0.0;
   try {
     price = 1.0 / Math.sqrt(2.0 * Math.PI) * integrator.integrate(integrant, -limit, limit);
   } catch (final Exception e) {
     throw new RuntimeException(e);
   }
   return price;
 }
 /**
  * Computes the present value sensitivity to the SABR parameters of a CMS cap/floor by replication
  * in SABR framework with extrapolation on the right.
  *
  * @param cmsCapFloor The CMS cap/floor.
  * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function.
  * @return The present value sensitivity to SABR parameters.
  */
 @Override
 public PresentValueSABRSensitivityDataBundle presentValueSABRSensitivity(
     final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
   final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
   final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
   final double forward = underlyingSwap.accept(PRC, sabrData);
   final double discountFactorTp =
       sabrData
           .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
           .getDiscountFactor(cmsCapFloor.getPaymentTime());
   final double strike = cmsCapFloor.getStrike();
   final double maturity =
       underlyingSwap
               .getFixedLeg()
               .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
               .getPaymentTime()
           - cmsCapFloor.getSettlementTime();
   final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
   final double alpha = sabrParameter.getAlpha(expiryMaturity);
   final double beta = sabrParameter.getBeta(expiryMaturity);
   final double rho = sabrParameter.getRho(expiryMaturity);
   final double nu = sabrParameter.getNu(expiryMaturity);
   final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
   final CMSVegaIntegrant integrantVega =
       new CMSVegaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
   final double factor = discountFactorTp / integrantVega.h(forward) * integrantVega.g(forward);
   final SABRExtrapolationRightFunction sabrExtrapolation =
       new SABRExtrapolationRightFunction(
           forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu);
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap());
   final double factor2 = factor * integrantVega.k(strike);
   final double[] strikePartPrice = new double[4];
   sabrExtrapolation.priceAdjointSABR(option, strikePartPrice);
   for (int loopvega = 0; loopvega < 4; loopvega++) {
     strikePartPrice[loopvega] *= factor2;
   }
   final double absoluteTolerance =
       1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
   final double relativeTolerance = 1E-3;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
   final double[] integralPart = new double[4];
   final double[] totalSensi = new double[4];
   for (int loopparameter = 0; loopparameter < 4; loopparameter++) {
     integrantVega.setParameterIndex(loopparameter);
     try {
       if (cmsCapFloor.isCap()) {
         integralPart[loopparameter] =
             discountFactorTp
                 * integrator.integrate(integrantVega, strike, strike + getIntegrationInterval());
       } else {
         integralPart[loopparameter] =
             discountFactorTp * integrator.integrate(integrantVega, 0.0, strike);
       }
     } catch (final Exception e) {
       throw new RuntimeException(e);
     }
     totalSensi[loopparameter] =
         (strikePartPrice[loopparameter] + integralPart[loopparameter])
             * cmsCapFloor.getNotional()
             * cmsCapFloor.getPaymentYearFraction();
   }
   final PresentValueSABRSensitivityDataBundle sensi = new PresentValueSABRSensitivityDataBundle();
   sensi.addAlpha(expiryMaturity, totalSensi[0]);
   sensi.addBeta(expiryMaturity, totalSensi[1]);
   sensi.addRho(expiryMaturity, totalSensi[2]);
   sensi.addNu(expiryMaturity, totalSensi[3]);
   return sensi;
 }
 /**
  * Computes the present value sensitivity to the yield curves of a CMS cap/floor by replication in
  * the SABR framework with extrapolation on the right.
  *
  * @param cmsCapFloor The CMS cap/floor.
  * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function.
  * @return The present value sensitivity to curves.
  */
 @Override
 public InterestRateCurveSensitivity presentValueCurveSensitivity(
     final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
   Validate.notNull(cmsCapFloor);
   Validate.notNull(sabrData);
   final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
   final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
   final double forward = underlyingSwap.accept(PRC, sabrData);
   final double discountFactor =
       sabrData
           .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
           .getDiscountFactor(cmsCapFloor.getPaymentTime());
   final double strike = cmsCapFloor.getStrike();
   final double maturity =
       underlyingSwap
               .getFixedLeg()
               .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
               .getPaymentTime()
           - cmsCapFloor.getSettlementTime();
   final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
   final double alpha = sabrParameter.getAlpha(expiryMaturity);
   final double beta = sabrParameter.getBeta(expiryMaturity);
   final double rho = sabrParameter.getRho(expiryMaturity);
   final double nu = sabrParameter.getNu(expiryMaturity);
   final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
   // Common
   final CMSIntegrant integrantPrice =
       new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
   final CMSDeltaIntegrant integrantDelta =
       new CMSDeltaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
   final double factor = discountFactor / integrantDelta.h(forward) * integrantDelta.g(forward);
   final double absoluteTolerance =
       1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
   final double relativeTolerance = 1E-10;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
   // Price
   final double[] bs = integrantDelta.bsbsp(strike);
   @SuppressWarnings("synthetic-access")
   final double[] n = integrantDelta.nnp(forward);
   final double strikePartPrice = discountFactor * integrantDelta.k(strike) * n[0] * bs[0];
   double integralPartPrice;
   try {
     if (cmsCapFloor.isCap()) {
       integralPartPrice =
           discountFactor
               * integrator.integrate(integrantPrice, strike, strike + getIntegrationInterval());
     } else {
       integralPartPrice = discountFactor * integrator.integrate(integrantPrice, 0.0, strike);
     }
   } catch (final Exception e) {
     throw new RuntimeException(e);
   }
   final double price =
       (strikePartPrice + integralPartPrice)
           * cmsCapFloor.getNotional()
           * cmsCapFloor.getPaymentYearFraction();
   // Delta
   final double strikePart =
       discountFactor * integrantDelta.k(strike) * (n[1] * bs[0] + n[0] * bs[1]);
   double integralPart;
   try {
     if (cmsCapFloor.isCap()) {
       integralPart =
           discountFactor
               * integrator.integrate(integrantDelta, strike, strike + getIntegrationInterval());
     } else {
       integralPart = discountFactor * integrator.integrate(integrantDelta, 0.0, strike);
     }
   } catch (final Exception e) {
     throw new RuntimeException(e);
   }
   final double deltaS0 =
       (strikePart + integralPart)
           * cmsCapFloor.getNotional()
           * cmsCapFloor.getPaymentYearFraction();
   final double deltaPD = price / discountFactor;
   final double sensiDF = -cmsCapFloor.getPaymentTime() * discountFactor * deltaPD;
   final List<DoublesPair> list = new ArrayList<>();
   list.add(new DoublesPair(cmsCapFloor.getPaymentTime(), sensiDF));
   final Map<String, List<DoublesPair>> resultMap = new HashMap<>();
   resultMap.put(
       cmsCapFloor.getUnderlyingSwap().getFixedLeg().getNthPayment(0).getFundingCurveName(), list);
   InterestRateCurveSensitivity result = new InterestRateCurveSensitivity(resultMap);
   final InterestRateCurveSensitivity forwardDr =
       new InterestRateCurveSensitivity(cmsCapFloor.getUnderlyingSwap().accept(PRSC, sabrData));
   result = result.plus(forwardDr.multipliedBy(deltaS0));
   return result;
 }
  /**
   * Computes the present value of an Ibor cap/floor in arrears by replication based on the paper,
   * "Swap and Cap/Floors with Fixing in Arrears or Payment Delay," OpenGamma Quantitative
   * Documentation
   * http://developers.opengamma.com/quantitative-research/In-Arrears-and-Payment-Delay-Swaps-and-Caps-OpenGamma.pdf
   *
   * @param cap The cap/floor
   * @param curves The curves
   * @return The present value
   */
  public MultipleCurrencyAmount presentValue(
      final CapFloorIbor cap, final MulticurveProviderInterface curves) {
    ArgumentChecker.notNull(cap, "The cap/floor shoud not be null");
    ArgumentChecker.notNull(curves, "curves");
    final Currency ccy = cap.getCurrency();
    // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
    CapFloorIbor capStandard =
        new CapFloorIbor(
            cap.getCurrency(),
            cap.getFixingPeriodEndTime(),
            cap.getPaymentYearFraction(),
            cap.getNotional(),
            cap.getFixingTime(),
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor(),
            cap.getStrike(),
            cap.isCap());
    final double forward =
        curves.getSimplyCompoundForwardRate(
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor());
    final double beta =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    final double df =
        curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime());
    final double strikePart =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());

    final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves);
    double integralPart;
    try {
      if (cap.isCap()) {
        double atmVol = _smileFunction.getVolatility(forward);
        double upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime()));
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, strike, upper);
        double reminder = integrant.evaluate(upper) * upper;
        double error = reminder / integralPart;

        int count = 0;
        while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) {
          integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper);
          upper *= 2.0;
          // The increase of integralPart in the next loop is bounded by reminder
          reminder = integrant.evaluate(upper) * upper;
          error = reminder / integralPart;
          ++count;
          if (count == MAX_COUNT) {
            LOGGER.info(
                "Maximum iteration count, "
                    + MAX_COUNT
                    + ", has been reached. Relative error is greater than "
                    + REL_ERROR);
          }
        }
      } else {
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPart *= 2.0 * cap.getFixingAccrualFactor();
    final double pv = (strikePart + integralPart) / beta;
    return MultipleCurrencyAmount.of(cap.getCurrency(), pv);
  }
  /**
   * Computes the pv sensitivity of an Ibor cap/floor in arrears
   *
   * @param cap The cap/floor
   * @param curves The curves
   * @return The sensitivity
   */
  public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(
      final CapFloorIbor cap, final MulticurveProviderInterface curves) {
    ArgumentChecker.notNull(cap, "The cap/floor shoud not be null");
    ArgumentChecker.notNull(curves, "curves");
    final Currency ccy = cap.getCurrency();
    // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
    CapFloorIbor capStandard =
        new CapFloorIbor(
            cap.getCurrency(),
            cap.getFixingPeriodEndTime(),
            cap.getPaymentYearFraction(),
            cap.getNotional(),
            cap.getFixingTime(),
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor(),
            cap.getStrike(),
            cap.isCap());
    final double forward =
        curves.getSimplyCompoundForwardRate(
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor());
    final double beta =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    double df = curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime());
    double strikePart =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());
    double strikePartDelta =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueDeltaStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());

    final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves);
    double integralPart;
    double upper = 0.0;
    try {
      if (cap.isCap()) {
        double atmVol = _smileFunction.getVolatility(forward);
        upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime()));
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, strike, upper);
        double reminder = integrant.evaluate(upper) * upper;
        double error = reminder / integralPart;

        int count = 0;
        while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) {
          integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper);
          upper *= 2.0;
          // The increase of integralPart in the next loop is bounded by reminder
          reminder = integrant.evaluate(upper) * upper;
          error = reminder / integralPart;
          ++count;
          if (count == MAX_COUNT) {
            LOGGER.info(
                "Maximum iteration count, "
                    + MAX_COUNT
                    + ", has been reached. Relative error is greater than "
                    + REL_ERROR);
          }
        }
      } else {
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPart *= 2.0 * cap.getFixingAccrualFactor();
    double pv = (strikePart + integralPart) / beta;

    double betaFwd =
        cap.getFixingAccrualFactor()
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());
    double betaDscStart =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            * cap.getFixingPeriodStartTime()
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());
    double betaDscEnd =
        -(1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            * cap.getFixingPeriodEndTime()
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    List<DoublesPair> listDiscounting = new ArrayList<>();
    double strikePartDsc = -capStandard.getPaymentTime() * strikePart;
    double integralPartDsc = -capStandard.getPaymentTime() * integralPart;
    listDiscounting.add(
        DoublesPair.of(capStandard.getPaymentTime(), (strikePartDsc + integralPartDsc) / beta));
    listDiscounting.add(DoublesPair.of(cap.getFixingPeriodStartTime(), -pv * betaDscStart / beta));
    listDiscounting.add(DoublesPair.of(cap.getFixingPeriodEndTime(), -pv * betaDscEnd / beta));
    Map<String, List<DoublesPair>> mapDsc = new HashMap<>();
    mapDsc.put(curves.getName(capStandard.getCurrency()), listDiscounting);

    final List<ForwardSensitivity> listForward = new ArrayList<>();
    double strikePartFwd = strikePartDelta;
    double integralPartFwd = 0.0;
    final InArrearsDeltaIntegrant integrantFwd = new InArrearsDeltaIntegrant(capStandard, curves);
    try {
      if (cap.isCap()) {
        double strike = cap.getStrike();
        integralPartFwd = INTEGRATOR.integrate(integrantFwd, strike, upper);
      } else {
        double strike = cap.getStrike();
        integralPartFwd = INTEGRATOR.integrate(integrantFwd, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPartFwd *= 2.0 * cap.getFixingAccrualFactor();
    listForward.add(
        new SimplyCompoundedForwardSensitivity(
            capStandard.getFixingPeriodStartTime(),
            capStandard.getFixingPeriodEndTime(),
            capStandard.getFixingAccrualFactor(),
            (strikePartFwd + integralPartFwd - pv * betaFwd) / beta));
    Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>();
    mapFwd.put(curves.getName(capStandard.getIndex()), listForward);

    return MultipleCurrencyMulticurveSensitivity.of(
        cap.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd));
  }