/**
 * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
 * 
 * Please see distribution for license.
 */
package com.opengamma.analytics.financial.interestrate.swap.provider;

import static org.testng.AssertJUnit.assertEquals;

import java.util.HashMap;
import java.util.Map;

import org.testng.annotations.Test;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;

import com.opengamma.analytics.env.AnalyticsEnvironment;
import com.opengamma.analytics.financial.datasets.CalendarGBP;
import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition;
import com.opengamma.analytics.financial.instrument.index.GeneratorAttributeIR;
import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIbor;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.analytics.financial.instrument.index.IndexIborMaster;
import com.opengamma.analytics.financial.instrument.payment.CouponFloatingDefinition;
import com.opengamma.analytics.financial.instrument.payment.PaymentDefinition;
import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborDefinition;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitor;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Coupon;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment;
import com.opengamma.analytics.financial.interestrate.swap.derivative.Swap;
import com.opengamma.analytics.financial.interestrate.swap.derivative.SwapFixedCoupon;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueCurveSensitivityDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.ParameterProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator;
import com.opengamma.analytics.financial.schedule.ScheduleCalculator;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.MatrixAlgebra;
import com.opengamma.analytics.math.matrix.OGMatrixAlgebra;
import com.opengamma.analytics.util.AssertMatrix;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.daycount.DayCounts;
import com.opengamma.timeseries.precise.zdt.ImmutableZonedDateTimeDoubleTimeSeries;
import com.opengamma.timeseries.precise.zdt.ZonedDateTimeDoubleTimeSeries;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.test.TestGroup;
import com.opengamma.util.time.DateUtils;

/**
 * GBP swap pricing example
 */
@Test(groups = TestGroup.UNIT)
public class GBPSwapPVTest {
  private static final InstrumentDerivativeVisitor<ParameterProviderInterface, MultipleCurrencyMulticurveSensitivity> PVCSDC = PresentValueCurveSensitivityDiscountingCalculator.getInstance();
  /** The parameter sensitivity calculator */
  private static final ParameterSensitivityParameterCalculator<ParameterProviderInterface> PSC = new ParameterSensitivityParameterCalculator<>(PVCSDC);

  private static final MatrixAlgebra MA = new OGMatrixAlgebra();

  private static final double NOTIONAL = 2e9;
  private static final Calendar baseCalendar = new CalendarGBP("GBP");
  private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance();
  private static final ZonedDateTime TRADE_DATE = DateUtils.getUTCDate(2014, 10, 6);

  // this is the single curve for IM
  private static final double[] GBP_LIBOR_KNOT_TIMES = new double[] {0.002739726027397, 0.019178082191781, 0.082191780821918, 0.164383561643836, 0.249315068493151, 0.498630136986301,
    0.747945205479452, 1, 1.4986301369863, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30, 35, 40, 45, 50 };
  private static final double[] GBP_LIBOR_ZERO_RATES = new double[] {0.00475626901070822, 0.00483107619161134, 0.00504645027587814, 0.00531456932134962, 0.00551204367300484, 0.0061026272153353,
    0.00684153896499341, 0.00765126878343251, 0.00940584623535082, 0.0111463649353094, 0.0151993630408899, 0.017520513620551, 0.0192799279116312, 0.0207051221084842, 0.0218964288340252,
    0.0229171448309875, 0.0238054222996467, 0.0245820797759827, 0.025876126744085, 0.0272638169223121, 0.0285519785481517, 0.0289179058336173, 0.0290094237232156, 0.028796927786485,
    0.0286085652864115, 0.0285639181095462, 0.0285319734875664 };

  // these multi curves are used for VM
  private static final double[] GBP_SONIA_KNOT_TIMES = new double[] {0.002739726027397, 0.019178082191781, 0.038356164383562, 0.084931506849315, 0.172602739726027, 0.252054794520548,
    0.336986301369863, 0.413698630136986, 0.501369863013699, 0.580821917808219, 0.671232876712329, 0.747945205479452, 0.832876712328767, 0.920547945205479, 1, 1.25205479452055, 1.5013698630137,
    1.75068493150685, 2.0027397260274, 3.0027397260274, 4.00821917808219, 5.00547945205479, 6.00547945205479, 7.00547945205479, 8.00547945205479, 9.00547945205479, 10.0109589041096, 12.0082191780822,
    15.0164383561644, 20.013698630137, 25.0164383561644, 30.0219178082192, 35.0246575342466, 40.027397260274, 45.0301369863014, 50.0356164383562 };
  private static final double[] GBP_SONIA_ZERO_RATES = new double[] {0.00430997455348493, 0.00431882113819674, 0.00431314320653989, 0.00431970749752497, 0.00444129725993973, 0.00447764229051919,
    0.00454934099818509, 0.00480522065276289, 0.00503962777637656, 0.00520911179002989, 0.00543690213062051, 0.00563610380093184, 0.00583579456204562, 0.00612718766319607, 0.00636519907010272,
    0.00717310918750252, 0.00795749811802235, 0.00870119236161661, 0.00946155387022128, 0.0123334511255753, 0.0143921749914485, 0.0159592596727554, 0.0172361877785297, 0.0183436791467994,
    0.0192793358953591, 0.0201222559973813, 0.0208986383710833, 0.0222510994961065, 0.023877100242259, 0.0256026136259111, 0.026288001664418, 0.0266311744778153, 0.0264014166694374,
    0.0263527169318637, 0.0262906111905873, 0.0263674066536486 };
  private static final double[] GBP_LIBOR1M_KNOT_TIMES = new double[] {0.084931506849315, 0.172602739726027, 0.252054794520548, 0.501369863013699, 0.747945205479452, 1, 2.0027397260274,
    3.0027397260274, 4.00821917808219, 5.00547945205479, 6.00547945205479, 7.00547945205479, 8.00547945205479, 9.00547945205479, 10.0109589041096, 12.0082191780822, 15.0164383561644, 20.013698630137,
    25.0164383561644, 30.0219178082192, 35.0246575342466, 40.027397260274, 45.0301369863014, 50.0356164383562 };
  private static final double[] GBP_LIBOR1M_ZERO_RATES = new double[] {0.00505581436649715, 0.00503291190352002, 0.00514256786002282, 0.00569033898394734, 0.00639149506676498, 0.00723232237139851,
    0.0105298436453575, 0.0135410574559515, 0.0158030551363141, 0.0174456995488895, 0.0187732338667046, 0.0199055537021988, 0.0208531180033394, 0.0216810024002466, 0.0224286195281712,
    0.0237374945104143, 0.0252160567356682, 0.0267142361493482, 0.0272605000077873, 0.0275097501922553, 0.0272973928154239, 0.02732399981293, 0.0272679024586238, 0.0273500955580994 };
  private static final double[] GBP_LIBOR3M_KNOT_TIMES = new double[] {0.252054794520548, 0.446575342465753, 0.695890410958904, 0.945205479452055, 1.19452054794521, 1.44383561643836,
    1.69315068493151, 1.96164383561644, 2.21095890410959, 3.0027397260274, 4.00821917808219, 5.00547945205479, 6.00547945205479, 7.00547945205479, 8.00547945205479, 9.00547945205479,
    10.0109589041096, 12.0082191780822, 15.0164383561644, 20.013698630137, 25.0164383561644, 30.0219178082192, 35.0246575342466, 40.027397260274, 45.0301369863014, 50.0356164383562 };
  private static final double[] GBP_LIBOR3M_ZERO_RATES = new double[] {0.00562351263918076, 0.00605140841711946, 0.00674163439039939, 0.00751172986806733, 0.00835257242596894, 0.00924368840059903,
    0.0101182341187527, 0.0110392349927795, 0.0118815761688227, 0.0141891783018791, 0.0164362685797301, 0.0180908568757903, 0.0194306164834304, 0.0205622948941999, 0.021509365473766,
    0.0223369044043144, 0.0230980379156519, 0.0243918201709714, 0.0258555923419827, 0.0273230838882434, 0.0278368027650289, 0.0280342598539863, 0.0278259808180962, 0.0277923406323035,
    0.0277421859831982, 0.0278284728253182 };
  private static final double[] GBP_LIBOR6M_KNOT_TIMES = new double[] {0.501369863013699, 1, 1.5013698630137, 2.0027397260274, 3.0027397260274, 4.00821917808219, 5.00547945205479, 6.00547945205479,
    7.00547945205479, 8.00547945205479, 9.00547945205479, 10.0109589041096, 12.0082191780822, 15.0164383561644, 20.013698630137, 25.0164383561644, 30.0219178082192, 35.0246575342466, 40.027397260274,
    45.0301369863014, 50.0356164383562 };
  private static final double[] GBP_LIBOR6M_ZERO_RATES = new double[] {0.00710174175249047, 0.00860451137005419, 0.0103551375418353, 0.0121086290753976, 0.015178756626544, 0.0175003704137641,
    0.0192427630940745, 0.0206581597595687, 0.0218403443388774, 0.0228520936339576, 0.0237315739821965, 0.0245039864998691, 0.025779797672448, 0.0271525952264722, 0.0284196247149596,
    0.0287909199539885, 0.0288932996886611, 0.0286980928245931, 0.0285278998759854, 0.0284929581122082, 0.0284672034337625 };

  private static final YieldAndDiscountCurve GBP_LIBOR_CURVE;
  private static final YieldAndDiscountCurve GBP_SONIA_CURVE;
  private static final YieldAndDiscountCurve GBP_LIBOR1M_CURVE;
  private static final YieldAndDiscountCurve GBP_LIBOR3M_CURVE;
  private static final YieldAndDiscountCurve GBP_LIBOR6M_CURVE;

  private static final MulticurveProviderDiscount SINGLE_CURVE;
  private static final MulticurveProviderDiscount MULTI_CURVE;
  private static final IndexIborMaster MASTER_IBOR = IndexIborMaster.getInstance();
  private static final IborIndex GBPLIBOR1M = MASTER_IBOR.getIndex("GBPLIBOR1M");
  private static final IborIndex GBPLIBOR3M = MASTER_IBOR.getIndex("GBPLIBOR3M");
  private static final IborIndex GBPLIBOR6M = MASTER_IBOR.getIndex("GBPLIBOR6M");
  private static final Currency CCY = GBPLIBOR1M.getCurrency();

  private static final ZonedDateTime[] FIXING_DATES = new ZonedDateTime[] {DateUtils.getUTCDate(2014, 8, 18), DateUtils.getUTCDate(2014, 10, 6) };

  private static final ZonedDateTimeDoubleTimeSeries TS_GBPLIBOR3M = ImmutableZonedDateTimeDoubleTimeSeries.ofUTC(FIXING_DATES, new double[] {0.005605, 0.0056275 });
  private static final ZonedDateTimeDoubleTimeSeries TS_GBPLIBOR6M = ImmutableZonedDateTimeDoubleTimeSeries.ofUTC(FIXING_DATES, new double[] {0.0069775, 0.0071144 });
  private static final Map<IborIndex, ZonedDateTimeDoubleTimeSeries> FIXINGS;
  private static final Interpolator1D IM_INTERPOLATOR;
  private static final Interpolator1D VM_INTERPOLATOR;
  static {
    FIXINGS = new HashMap<>(4);
    FIXINGS.put(GBPLIBOR3M, TS_GBPLIBOR3M);
    FIXINGS.put(GBPLIBOR6M, TS_GBPLIBOR6M);
    IM_INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.LINEAR, Interpolator1DFactory.FLAT_EXTRAPOLATOR);
    VM_INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory.getInterpolator(
        Interpolator1DFactory.PRODUCT_NATURAL_CUBIC, Interpolator1DFactory.FLAT_EXTRAPOLATOR,
        Interpolator1DFactory.RECIPROCAL_EXTRAPOLATOR);
    GBP_LIBOR_CURVE = new YieldCurve("GBP Libor", new InterpolatedDoublesCurve(GBP_LIBOR_KNOT_TIMES, GBP_LIBOR_ZERO_RATES, IM_INTERPOLATOR, true));
    GBP_SONIA_CURVE = new YieldCurve("GBP Sonia", new InterpolatedDoublesCurve(GBP_SONIA_KNOT_TIMES, GBP_SONIA_ZERO_RATES, VM_INTERPOLATOR, true));
    GBP_LIBOR1M_CURVE = new YieldCurve("GBP Libor 1M", new InterpolatedDoublesCurve(GBP_LIBOR1M_KNOT_TIMES, GBP_LIBOR1M_ZERO_RATES, VM_INTERPOLATOR, true));
    GBP_LIBOR3M_CURVE = new YieldCurve("GBP Libor 3M", new InterpolatedDoublesCurve(GBP_LIBOR3M_KNOT_TIMES, GBP_LIBOR3M_ZERO_RATES, VM_INTERPOLATOR, true));
    GBP_LIBOR6M_CURVE = new YieldCurve("GBP Libor 6M", new InterpolatedDoublesCurve(GBP_LIBOR6M_KNOT_TIMES, GBP_LIBOR6M_ZERO_RATES, VM_INTERPOLATOR, true));

    @SuppressWarnings("unused")
    YieldCurve zero = new YieldCurve("zero", ConstantDoublesCurve.from(0.0));
    SINGLE_CURVE = new MulticurveProviderDiscount();
    SINGLE_CURVE.setOrReplaceCurve(CCY, GBP_LIBOR_CURVE);
    SINGLE_CURVE.setOrReplaceCurve(GBPLIBOR1M, GBP_LIBOR_CURVE);
    SINGLE_CURVE.setOrReplaceCurve(GBPLIBOR3M, GBP_LIBOR_CURVE);
    SINGLE_CURVE.setOrReplaceCurve(GBPLIBOR6M, GBP_LIBOR_CURVE);
    MULTI_CURVE = new MulticurveProviderDiscount();
    MULTI_CURVE.setOrReplaceCurve(CCY, GBP_SONIA_CURVE);
    MULTI_CURVE.setOrReplaceCurve(GBPLIBOR1M, GBP_LIBOR1M_CURVE);
    MULTI_CURVE.setOrReplaceCurve(GBPLIBOR3M, GBP_LIBOR3M_CURVE);
    MULTI_CURVE.setOrReplaceCurve(GBPLIBOR6M, GBP_LIBOR6M_CURVE);
  }

  /**
   * Test the IM NPV, delta & gamma for a seasoned swap.
   * This is a 3M fixed Vs float with a coupon of 2% - the effective date is 18-Feb-2014 and the termination date is the
   * 18-Feb-2019 (i.e. this was originally a 5Y swap)
   */
  @Test
  public void testSeasonedSwap() {
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.getInstance().toBuilder().modelDayCount(DayCounts.ACT_365)
        .build());
    double coupon = 0.02;
    ZonedDateTime effDate = DateUtils.getUTCDate(2014, 2, 18);
    Period swapTenor = Period.ofYears(5); // termination date 18/02/2019
    SwapFixedIborDefinition swapDef = makeSwapDef(GBPLIBOR3M, effDate, swapTenor, coupon, NOTIONAL);

    double expImNpv = -2.012506863816616E7;
    double expVmNpv = -2.92490786938425E7;
    DoubleMatrix1D expDelta = new DoubleMatrix1D(0, 0, -13295.06565595203, -10166.814913375081, 192.18569490757258, 500.95144492207777, 737.1719141579288, 1610.7473112728212, 2960.684803071864,
        5797.798336051921, 12428.744989705048, 517803.6258600666, 304194.28278619424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    DoubleMatrix1D expGamma = new DoubleMatrix1D(0, 0, 0.1566268008783391, 0.11977343596578863, -0.007108238030828089, -0.02635626715757221, -0.056885092021793426, -0.1787119119364074,
        -0.45267430946650566, -1.3767795332248913, -3.8464351748222136, -225.74022089673812, -132.8577895249877, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    testImPriceAndDelta(swapDef, TRADE_DATE, expImNpv, expDelta, expGamma);
    testVmNpv(swapDef, TRADE_DATE, expVmNpv);
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.DEFAULT);
  }

  /**
   * Test the IM NPV, delta & gamma for a spot starting 5Y swap.
   * This is a 3M fixed Vs float with a coupon of 1.8% - the effective date is 6-Oct-2014 (i.e. today) and the termination
   * date is the 16-Oct-2019
   */
  @Test
  public void testSpotStarting5YSwap() {
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.getInstance().toBuilder().modelDayCount(DayCounts.ACT_365)
        .build());
    double coupon = 0.018;
    ZonedDateTime effDate = DateUtils.getUTCDate(2014, 10, 6);
    Period swapTenor = Period.ofYears(5); // termination date 6/10/2019
    SwapFixedIborDefinition swapDef = makeSwapDef(GBPLIBOR3M, effDate, swapTenor, coupon, NOTIONAL);

    // these number come from the SMART API
    double expImNpv = 1.1050631904386938E7;
    double expVmNpv =  -75412.71316513419;
    DoubleMatrix1D expDelta = new DoubleMatrix1D(0, 0, 0, 0, -49632.4255084524, 976.208958972902, -2026.74477030921, 3065.41427124944, 2656.9664567835, 5701.96379992345, 9906.05728427153,
        15976.3010680254, 909245.758217927, 7433.36153796299, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    DoubleMatrix1D expGamma = new DoubleMatrix1D(0, 0, 0, 0, 1.25100908130893, -0.0894835162024819, 0.125063124838612, -0.239876495266289, -0.403875990484971, -1.25711576707553, -3.11758912386388,
        -6.5416697665622, -454.865666666857, -3.6581303462374, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    testImPriceAndDelta(swapDef, TRADE_DATE, expImNpv, expDelta, expGamma);
    testVmNpv(swapDef, TRADE_DATE, expVmNpv);
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.DEFAULT);
  }

  /**
   * Test the IM NPV, delta & gamma for a spot starting 10Y swap.
   * This is a 6M fixed Vs float with a coupon of 1.8% - the effective date is 6-Oct-2014 (i.e. today) and the termination
   * date is the 16-Oct-2024
   */
  @Test
  public void testSpotStarting10YSwap() {
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.getInstance().toBuilder().modelDayCount(DayCounts.ACT_365)
        .build());
    double coupon = 0.03;
    ZonedDateTime effDate = DateUtils.getUTCDate(2014, 10, 6);
    Period swapTenor = Period.ofYears(10); // termination date 6/10/2024
    SwapFixedIborDefinition swapDef = makeSwapDef(GBPLIBOR6M, effDate, swapTenor, coupon, NOTIONAL);

    // these number come from the SMART API
    double expImNpv = -1.0222040526150137E8;
    double expVmNpv = -1.0496644868809742E8;
    DoubleMatrix1D expDelta = new DoubleMatrix1D(0, 0, 0, 0, 0, -97196.9994870792, -1079.96666096754, 1335.14030038791, 5521.29236720072, 9510.26367240674, 17185.06655544, 24577.9320516185,
        22254.5350746961, 34549.256798932, 36014.5418502935, 39942.877027383, 47537.8635419423, 1585829.99987959, 10828.3857355132, 0, 0, 0, 0, 0, 0, 0, 0);
    DoubleMatrix1D expGamma = new DoubleMatrix1D(0, 0, 0, 0, 0, 4.84622451300801, 0.0538469390334223, -0.161052818155604, -0.719393713339903, -2.0834698170384, -5.22254635716672, -10.1242640156405,
        -11.3974801071293, -20.2488639790105, -25.2621973392004, -32.0024263476015, -43.2417411211072, -1587.05920064115, -10.7314585575958, 0, 0, 0, 0, 0, 0, 0, 0);
    testImPriceAndDelta(swapDef, TRADE_DATE, expImNpv, expDelta, expGamma);

    testVmNpv(swapDef, TRADE_DATE, expVmNpv);
    AnalyticsEnvironment.setInstance(AnalyticsEnvironment.DEFAULT);
  }

  /**
   * Print multicurve, for debugging
   */
  @Test(enabled = false)
  public void printMulticurve() {
    System.out.println(SINGLE_CURVE);
    System.out.println(CCY);
    System.out.println(GBPLIBOR1M.getDayCount() + "\t" + GBPLIBOR1M.getTenor() + "\t" + GBPLIBOR1M.getSpotLag());
    System.out.println(GBPLIBOR3M.getDayCount() + "\t" + GBPLIBOR3M.getTenor() + "\t" + GBPLIBOR3M.getSpotLag());
    System.out.println(GBPLIBOR3M);
  }

  private void testImPriceAndDelta(SwapFixedIborDefinition swapDef, ZonedDateTime tradeDate, double expImNpv, DoubleMatrix1D expDelta, DoubleMatrix1D expGamma) {
    double oneBps = 1e-4;
    double oneBpsSqr = 1e-8;
    IborIndex index = swapDef.getIborLeg().getIborIndex();
    ZonedDateTimeDoubleTimeSeries fixings = FIXINGS.get(index);
    SwapFixedCoupon<Coupon> swap = swapDef.toDerivative(tradeDate, new ZonedDateTimeDoubleTimeSeries[] {fixings });
    MultipleCurrencyAmount price = swap.accept(PVDC, SINGLE_CURVE);
    assertEquals(expImNpv, price.getAmount(CCY), 1e-15 * NOTIONAL);

    Function1D<DoubleMatrix1D, DoubleMatrix1D> func = getDeltaFunction(swap, index, GBP_LIBOR_KNOT_TIMES, IM_INTERPOLATOR);
    DoubleMatrix1D delta = func.evaluate(new DoubleMatrix1D(GBP_LIBOR_ZERO_RATES));
    // scale to be in bps
    delta = (DoubleMatrix1D) MA.scale(delta, oneBps);
    if (expDelta != null) {
      AssertMatrix.assertEqualsVectors(expDelta, delta, 1e-13 * NOTIONAL * oneBps);
    } else {
      System.out.println("Calcualted delta: " + delta);
    }

    DoubleMatrix1D gamma = getColumnGamma(swap, index, GBP_LIBOR_KNOT_TIMES, GBP_LIBOR_ZERO_RATES, IM_INTERPOLATOR);
    // scale to be in bps^2
    gamma = (DoubleMatrix1D) MA.scale(gamma, oneBpsSqr);
    // gamma is calculated by finite-difference (on the delta) so the accuracy will not be as high
    if (expGamma != null) {
      AssertMatrix.assertEqualsVectors(expGamma, gamma, 1e-8 * NOTIONAL * oneBpsSqr);
    } else {
      System.out.println("Calcualted gamma: " + gamma);
    }
  }

  private void testVmNpv(SwapFixedIborDefinition swapDef, ZonedDateTime tradeDate, double expVmNpv) {
    IborIndex index = swapDef.getIborLeg().getIborIndex();
    ZonedDateTimeDoubleTimeSeries fixings = FIXINGS.get(index);
    SwapFixedCoupon<Coupon> swap = swapDef.toDerivative(tradeDate, new ZonedDateTimeDoubleTimeSeries[] {fixings });
    MultipleCurrencyAmount price = swap.accept(PVDC, MULTI_CURVE);
    assertEquals(expVmNpv, price.getAmount(CCY), 1e-15 * NOTIONAL);
  }

  private SwapFixedIborDefinition makeSwapDef(IborIndex index, ZonedDateTime effDate, Period swapTenor, double coupon, double notional) {
    // This just ensures the the swap does start on the spot date
    ZonedDateTime refDate = ScheduleCalculator.getAdjustedDate(effDate, -index.getSpotLag(), baseCalendar);
    GeneratorAttributeIR att = new GeneratorAttributeIR(swapTenor);
    Period tenor = index.getTenor();
    GeneratorSwapFixedIbor swapGen = new GeneratorSwapFixedIbor(index.toString() + " Swap", tenor, index.getDayCount(), index, baseCalendar);
    return swapGen.generateInstrument(refDate, coupon, notional, att);
  }

  /**
   * Creates a function that returns the delta (swap PV's sensitivity to the zero rates) for a set of zero rates
   * @param swap The swap
   * @param index The Ibor-index of the swap
   * @param t The curve knot times
   * @param interpolator The interpolator used for the curve
   * @return function that maps zero rates to deltas
   */
  private Function1D<DoubleMatrix1D, DoubleMatrix1D> getDeltaFunction(final Swap<? extends Payment, ? extends Payment> swap, final IborIndex index, final double[] t, final Interpolator1D interpolator) {
    return new Function1D<DoubleMatrix1D, DoubleMatrix1D>() {
      String curveName = "GBP Libor";

      @Override
      public DoubleMatrix1D evaluate(DoubleMatrix1D zeroRates) {
        YieldCurve curve = new YieldCurve(curveName, new InterpolatedDoublesCurve(t, zeroRates.getData(), interpolator, true));
        MulticurveProviderDiscount multiCurve = new MulticurveProviderDiscount();
        multiCurve.setOrReplaceCurve(CCY, curve);
        multiCurve.setOrReplaceCurve(index, curve);
        MultipleCurrencyParameterSensitivity senseZeroRates = PSC.calculateSensitivity(swap, multiCurve);
        return senseZeroRates.getSensitivityByName(curveName).get(CCY);
      }
    };
  }

  /**
   * The column summed gamma is the sum of the column of the full gamma matrix (second order sensitivity of the swap PV to the
   * zero rates). Here we calculate it by bumping the zeros rates up and down in parallel by a small amount, eps.
   * @param swap The swap
   * @param index The Ibor-index of the swap
   * @param t The curve knot times
   * @param r The curve sezo rates
   * @param interpolator The interpolator used for the curve
   * @return The (column summed) gamma for a swap
   */
  private DoubleMatrix1D getColumnGamma(final Swap<? extends Payment, ? extends Payment> swap, final IborIndex index, final double[] t, final double[] r, final Interpolator1D interpolator) {
    double eps = 1e-6;
    double scale = 1 / 2. / eps;
    final int n = r.length;
    final DoubleMatrix1D zeroRates = new DoubleMatrix1D(r);
    final Function1D<DoubleMatrix1D, DoubleMatrix1D> deltaFunc = getDeltaFunction(swap, index, t, interpolator);

    Function1D<Double, DoubleMatrix1D> ParellelDeltaFunc = new Function1D<Double, DoubleMatrix1D>() {
      @Override
      public DoubleMatrix1D evaluate(Double shift) {
        DoubleMatrix1D shifts = new DoubleMatrix1D(n, shift);
        return deltaFunc.evaluate((DoubleMatrix1D) MA.add(shifts, zeroRates));
      }
    };

    DoubleMatrix1D up = ParellelDeltaFunc.evaluate(eps);
    DoubleMatrix1D down = ParellelDeltaFunc.evaluate(-eps);
    return (DoubleMatrix1D) MA.scale(MA.subtract(up, down), scale);
  }

  /*
   * The function used for debugging
   */
  @SuppressWarnings("unused")
  private ZonedDateTime getLastFixing(AnnuityDefinition<?> leg, ZonedDateTime now) {
    for (PaymentDefinition p : leg.getPayments()) {
      if (p.getPaymentDate().isAfter(now)) {
        return ((CouponFloatingDefinition) p).getFixingDate();
      }
    }
    throw new IllegalArgumentException("all payments in past");
  }

}
