/**
  * Compute the present value sensitivity to rates of a bond future by discounting.
  *
  * @param future The future.
  * @param issuerMulticurves The issuer and multi-curves provider.
  * @return The present value rate sensitivity.
  */
 public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(
     final BondFuture future, final IssuerProviderInterface issuerMulticurves) {
   Currency ccy = future.getCurrency();
   final MulticurveSensitivity priceSensitivity = priceCurveSensitivity(future, issuerMulticurves);
   final MultipleCurrencyMulticurveSensitivity transactionSensitivity =
       MultipleCurrencyMulticurveSensitivity.of(
           ccy, priceSensitivity.multipliedBy(future.getNotional()));
   return transactionSensitivity;
 }
 /**
  * Compute the present value sensitivity to rates of a Ibor compounded coupon by discounting.
  *
  * @param coupon The coupon.
  * @param multicurve The multi-curve provider.
  * @return The present value sensitivity.
  */
 public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(
     final CouponIborCompounding coupon, final MulticurveProviderInterface multicurve) {
   ArgumentChecker.notNull(coupon, "Coupon");
   ArgumentChecker.notNull(multicurve, "Multi-curves provider");
   final int nbSubPeriod = coupon.getFixingTimes().length;
   double notionalAccrued = coupon.getNotionalAccrued();
   final double[] forward = new double[nbSubPeriod];
   final double[] ratioForward = new double[nbSubPeriod];
   for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
     forward[loopsub] =
         multicurve.getForwardRate(
             coupon.getIndex(),
             coupon.getFixingPeriodStartTimes()[loopsub],
             coupon.getFixingPeriodEndTimes()[loopsub],
             coupon.getFixingPeriodAccrualFactors()[loopsub]);
     ratioForward[loopsub] = 1.0 + coupon.getPaymentAccrualFactors()[loopsub] * forward[loopsub];
     notionalAccrued *= ratioForward[loopsub];
   }
   final double dfPayment =
       multicurve.getDiscountFactor(coupon.getCurrency(), coupon.getPaymentTime());
   // Backward sweep
   final double pvBar = 1.0;
   final double dfPaymentBar = (notionalAccrued - coupon.getNotional()) * pvBar;
   final double notionalAccruedBar = dfPayment * pvBar;
   final double[] ratioForwardBar = new double[nbSubPeriod];
   final double[] forwardBar = new double[nbSubPeriod];
   for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
     ratioForwardBar[loopsub] = notionalAccrued / ratioForward[loopsub] * notionalAccruedBar;
     forwardBar[loopsub] = coupon.getPaymentAccrualFactors()[loopsub] * ratioForwardBar[loopsub];
   }
   final Map<String, List<DoublesPair>> mapDsc = new HashMap<>();
   final List<DoublesPair> listDiscounting = new ArrayList<>();
   listDiscounting.add(
       new DoublesPair(
           coupon.getPaymentTime(), -coupon.getPaymentTime() * dfPayment * dfPaymentBar));
   mapDsc.put(multicurve.getName(coupon.getCurrency()), listDiscounting);
   final Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>();
   final List<ForwardSensitivity> listForward = new ArrayList<>();
   for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
     listForward.add(
         new ForwardSensitivity(
             coupon.getFixingPeriodStartTimes()[loopsub],
             coupon.getFixingPeriodEndTimes()[loopsub],
             coupon.getFixingPeriodAccrualFactors()[loopsub],
             forwardBar[loopsub]));
   }
   mapFwd.put(multicurve.getName(coupon.getIndex()), listForward);
   return MultipleCurrencyMulticurveSensitivity.of(
       coupon.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd));
 }
 /**
  * Computes the present value curve sensitivity of a fixed payment by discounting.
  *
  * @param payment The fixed payment.
  * @param multicurves The multi-curve provider.
  * @return The sensitivity.
  */
 public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(
     final PaymentFixed payment, final MulticurveProviderInterface multicurves) {
   final double time = payment.getPaymentTime();
   final DoublesPair s =
       DoublesPair.of(
           time,
           -time
               * payment.getAmount()
               * multicurves.getDiscountFactor(payment.getCurrency(), time));
   final List<DoublesPair> list = new ArrayList<>();
   list.add(s);
   final Map<String, List<DoublesPair>> result = new HashMap<>();
   result.put(multicurves.getName(payment.getCurrency()), list);
   return MultipleCurrencyMulticurveSensitivity.of(
       payment.getCurrency(), MulticurveSensitivity.ofYieldDiscounting(result));
 }
  /**
   * 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));
  }