/**
  * Create an analytic swaption approximation product for log normal forward rate model.
  *
  * <p>Note: It is implicitly assumed that swapTenor.getTime(0) is the exercise date (no forward
  * starting).
  *
  * @param swaprate The strike swap rate of the swaption.
  * @param swapTenor The swap tenor in doubles.
  */
 public SwaptionAnalyticApproximation(double swaprate, TimeDiscretizationInterface swapTenor) {
   this(swaprate, swapTenor.getAsDoubleArray(), ValueUnit.VALUE);
 }
  /**
   * This function calculate the partial derivative <i>d log(S) / d log(L<sub>k</sub>)</i> for a
   * given swap rate with respect to a vector of forward rates (on a given forward rate tenor).
   *
   * <p>It also returns some useful other quantities like the corresponding discout factors and swap
   * annuities.
   *
   * @param liborPeriodDiscretization The libor period discretization.
   * @param discountCurveInterface The discount curve. If this parameter is null, the discount curve
   *     will be calculated from the forward curve.
   * @param forwardCurveInterface The forward curve.
   * @return A map containing the partial derivatives (key "value"), the discount factors (key
   *     "discountFactors") and the annuities (key "annuities") as vectors of double[] (indexed by
   *     forward rate tenor index starting at swap start)
   */
  public Map<String, double[]> getLogSwaprateDerivative(
      TimeDiscretizationInterface liborPeriodDiscretization,
      DiscountCurveInterface discountCurveInterface,
      ForwardCurveInterface forwardCurveInterface) {

    /*
     * We cache the calculation of the log swaprate derivative. In a calibration this method might be called quite often with the same arguments.
     */
    synchronized (cachedLogSwaprateDerivativeLock) {
      if (cachedLogSwaprateDerivative != null
          && liborPeriodDiscretization == cachedLogSwaprateDerivativeTimeDiscretization.get()
          && discountCurveInterface == cachedLogSwaprateDerivativeDiscountCurve.get()
          && forwardCurveInterface == cachedLogSwaprateDerivativeForwardCurve.get()) {
        return cachedLogSwaprateDerivative;
      }
      cachedLogSwaprateDerivativeTimeDiscretization =
          new WeakReference<TimeDiscretizationInterface>(liborPeriodDiscretization);
      cachedLogSwaprateDerivativeDiscountCurve =
          new WeakReference<DiscountCurveInterface>(discountCurveInterface);
      cachedLogSwaprateDerivativeForwardCurve =
          new WeakReference<ForwardCurveInterface>(forwardCurveInterface);

      /*
       * Small workaround for the case that the discount curve is not set. This part will be removed later.
       */
      AnalyticModel model = null;
      if (discountCurveInterface == null) {
        discountCurveInterface = new DiscountCurveFromForwardCurve(forwardCurveInterface.getName());
        model =
            new AnalyticModel(new CurveInterface[] {forwardCurveInterface, discountCurveInterface});
      }

      double swapStart = swapTenor[0];
      double swapEnd = swapTenor[swapTenor.length - 1];

      // Get the indices of the swap start and end on the forward rate tenor
      int swapStartIndex = liborPeriodDiscretization.getTimeIndex(swapStart);
      int swapEndIndex = liborPeriodDiscretization.getTimeIndex(swapEnd);

      // Precalculate forward rates and discount factors. Note: the swap contains
      // swapEndIndex-swapStartIndex forward rates
      double[] forwardRates = new double[swapEndIndex - swapStartIndex + 1];
      double[] discountFactors = new double[swapEndIndex - swapStartIndex + 1];

      // Calculate discount factor at swap start
      discountFactors[0] = discountCurveInterface.getDiscountFactor(model, swapStart);

      // Calculate discount factors for swap period ends (used for swap annuity)
      for (int liborPeriodIndex = swapStartIndex;
          liborPeriodIndex < swapEndIndex;
          liborPeriodIndex++) {
        double libor =
            forwardCurveInterface.getForward(
                null, liborPeriodDiscretization.getTime(liborPeriodIndex));

        forwardRates[liborPeriodIndex - swapStartIndex] = libor;
        discountFactors[liborPeriodIndex - swapStartIndex + 1] =
            discountCurveInterface.getDiscountFactor(
                model, liborPeriodDiscretization.getTime(liborPeriodIndex + 1));
      }

      // Precalculate swap annuities
      double[] swapAnnuities = new double[swapTenor.length - 1];
      double swapAnnuity = 0.0;
      for (int swapPeriodIndex = swapTenor.length - 2; swapPeriodIndex >= 0; swapPeriodIndex--) {
        int periodEndIndex = liborPeriodDiscretization.getTimeIndex(swapTenor[swapPeriodIndex + 1]);
        swapAnnuity +=
            discountFactors[periodEndIndex - swapStartIndex]
                * (swapTenor[swapPeriodIndex + 1] - swapTenor[swapPeriodIndex]);
        swapAnnuities[swapPeriodIndex] = swapAnnuity;
      }

      // Precalculate weights: The formula is take from ISBN 0470047224
      double[] swapCovarianceWeights = new double[swapEndIndex - swapStartIndex];

      double valueFloatLeg = 0.0;
      for (int liborPeriodIndex = swapStartIndex;
          liborPeriodIndex < swapEndIndex;
          liborPeriodIndex++) {
        double liborPeriodLength = liborPeriodDiscretization.getTimeStep(liborPeriodIndex);
        valueFloatLeg +=
            forwardRates[liborPeriodIndex - swapStartIndex]
                * discountFactors[liborPeriodIndex - swapStartIndex + 1]
                * liborPeriodLength;
      }

      int swapPeriodIndex = 0;
      double valueFloatLegUpToSwapStart = 0.0;
      for (int liborPeriodIndex = swapStartIndex;
          liborPeriodIndex < swapEndIndex;
          liborPeriodIndex++) {
        if (liborPeriodDiscretization.getTime(liborPeriodIndex) >= swapTenor[swapPeriodIndex + 1])
          swapPeriodIndex++;

        double libor = forwardRates[liborPeriodIndex - swapStartIndex];
        double liborPeriodLength = liborPeriodDiscretization.getTimeStep(liborPeriodIndex);

        valueFloatLegUpToSwapStart +=
            forwardRates[liborPeriodIndex - swapStartIndex]
                * discountFactors[liborPeriodIndex - swapStartIndex + 1]
                * liborPeriodLength;

        double discountFactorAtPeriodEnd =
            discountCurveInterface.getDiscountFactor(
                model, liborPeriodDiscretization.getTime(liborPeriodIndex + 1));
        double derivativeFloatLeg =
            (discountFactorAtPeriodEnd + valueFloatLegUpToSwapStart - valueFloatLeg)
                * liborPeriodLength
                / (1.0 + libor * liborPeriodLength)
                / valueFloatLeg;
        double derivativeFixLeg =
            -swapAnnuities[swapPeriodIndex]
                / swapAnnuity
                * liborPeriodLength
                / (1.0 + libor * liborPeriodLength);

        swapCovarianceWeights[liborPeriodIndex - swapStartIndex] =
            (derivativeFloatLeg - derivativeFixLeg) * libor;
      }

      // Return results
      Map<String, double[]> results = new HashMap<String, double[]>();
      results.put("values", swapCovarianceWeights);
      results.put("discountFactors", discountFactors);
      results.put("swapAnnuities", swapAnnuities);

      cachedLogSwaprateDerivative = results;

      return results;
    }
  }