/** Test getDelta, getGamma and getVega */
  @Test
  public void greeksTest() {
    double tol = 1.0e-12;
    double eps = 1.0e-5;
    double[] priceDerivative = new double[3];
    for (EuropeanVanillaOption option :
        new EuropeanVanillaOption[] {ITM_CALL, ITM_PUT, OTM_CALL, OTM_PUT, ATM_CALL, ATM_PUT}) {
      // consistency with getPriceFunction for first order derivatives
      FUNCTION.getPriceAdjoint(option, VOL_DATA, priceDerivative);
      double delta = FUNCTION.getDelta(option, VOL_DATA);
      double vega = FUNCTION.getVega(option, VOL_DATA);
      assertEquals(delta, priceDerivative[0], tol);
      assertEquals(vega, priceDerivative[1], tol);

      // testing second order derivative against finite difference approximation
      NormalFunctionData dataUp = new NormalFunctionData(F + eps, DF, SIGMA);
      NormalFunctionData dataDw = new NormalFunctionData(F - eps, DF, SIGMA);
      double deltaUp = FUNCTION.getDelta(option, dataUp);
      double deltaDw = FUNCTION.getDelta(option, dataDw);
      double ref = 0.5 * (deltaUp - deltaDw) / eps;
      double gamma = FUNCTION.getGamma(option, VOL_DATA);
      assertEquals(ref, gamma, eps);

      EuropeanVanillaOption optionUp =
          new EuropeanVanillaOption(option.getStrike(), T + eps, option.isCall());
      EuropeanVanillaOption optionDw =
          new EuropeanVanillaOption(option.getStrike(), T - eps, option.isCall());
      double priceTimeUp = FUNCTION.getPriceAdjoint(optionUp, VOL_DATA, priceDerivative);
      double priceTimeDw = FUNCTION.getPriceAdjoint(optionDw, VOL_DATA, priceDerivative);
      ref = -0.5 * (priceTimeUp - priceTimeDw) / eps;
      double theta = FUNCTION.getTheta(option, VOL_DATA);
      assertEquals(ref, theta, eps);
    }
  }
  /** Testing the branch for sigmaRootT < 1e-16 */
  @Test
  public void smallParameterGreeksTest() {
    double eps = 1.0e-5;
    double[] der = new double[3];
    NormalFunctionData dataVolUp = new NormalFunctionData(F, DF, eps);
    NormalFunctionData dataFwUp = new NormalFunctionData(F + eps, DF, 0.0);
    NormalFunctionData dataFwDw = new NormalFunctionData(F - eps, DF, 0.0);

    for (EuropeanVanillaOption option :
        new EuropeanVanillaOption[] {ITM_CALL, ITM_PUT, OTM_CALL, OTM_PUT, ATM_CALL, ATM_PUT}) {
      double delta = FUNCTION.getDelta(option, ZERO_VOL_DATA);
      double priceUp = FUNCTION.getPriceAdjoint(option, dataFwUp, der);
      double priceDw = FUNCTION.getPriceAdjoint(option, dataFwDw, der);
      double refDelta = 0.5 * (priceUp - priceDw) / eps;
      assertEquals(refDelta, delta, eps);

      double vega = FUNCTION.getVega(option, ZERO_VOL_DATA);
      double priceVolUp = FUNCTION.getPriceAdjoint(option, dataVolUp, der);
      double price = FUNCTION.getPriceAdjoint(option, ZERO_VOL_DATA, der);
      double refVega = (priceVolUp - price) / eps;
      assertEquals(refVega, vega, eps);

      double gamma = FUNCTION.getGamma(option, ZERO_VOL_DATA);
      double deltaUp = FUNCTION.getDelta(option, dataFwUp);
      double deltaDw = FUNCTION.getDelta(option, dataFwDw);
      double refGamma = 0.5 * (deltaUp - deltaDw) / eps;
      if (Math.abs(refGamma) > 0.1 / eps) { // infinity handled
        assertTrue(Double.isInfinite(gamma));
      } else {
        assertEquals(refGamma, gamma, eps);
      }

      EuropeanVanillaOption optionUp =
          new EuropeanVanillaOption(option.getStrike(), T + eps, option.isCall());
      EuropeanVanillaOption optionDw =
          new EuropeanVanillaOption(option.getStrike(), T - eps, option.isCall());
      double priceTimeUp = FUNCTION.getPriceAdjoint(optionUp, ZERO_VOL_DATA, der);
      double priceTimeDw = FUNCTION.getPriceAdjoint(optionDw, ZERO_VOL_DATA, der);
      double refTheta = -0.5 * (priceTimeUp - priceTimeDw) / eps;
      double theta = FUNCTION.getTheta(option, ZERO_VOL_DATA);
      assertEquals(refTheta, theta, eps);
    }
  }
 @Test
 public void testPriceAdjoint() {
   // Price
   double price = FUNCTION.getPriceFunction(ITM_CALL).evaluate(VOL_DATA);
   double[] priceDerivative = new double[3];
   double priceAdjoint = FUNCTION.getPriceAdjoint(ITM_CALL, VOL_DATA, priceDerivative);
   assertEquals(price, priceAdjoint, 1E-10);
   // Price with 0 volatility
   double price0 = FUNCTION.getPriceFunction(ITM_CALL).evaluate(ZERO_VOL_DATA);
   double[] price0Derivative = new double[3];
   double price0Adjoint = FUNCTION.getPriceAdjoint(ITM_CALL, ZERO_VOL_DATA, price0Derivative);
   assertEquals(price0, price0Adjoint, 1E-10);
   // Derivative forward.
   double deltaF = 0.01;
   NormalFunctionData dataFP = new NormalFunctionData(F + deltaF, DF, SIGMA);
   NormalFunctionData dataFM = new NormalFunctionData(F - deltaF, DF, SIGMA);
   double priceFP = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataFP);
   double priceFM = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataFM);
   double derivativeF_FD = (priceFP - priceFM) / (2 * deltaF);
   assertEquals(derivativeF_FD, priceDerivative[0], 1E-7);
   // Derivative strike.
   double deltaK = 0.01;
   EuropeanVanillaOption optionKP = new EuropeanVanillaOption(F - DELTA + deltaK, T, true);
   EuropeanVanillaOption optionKM = new EuropeanVanillaOption(F - DELTA - deltaK, T, true);
   double priceKP = FUNCTION.getPriceFunction(optionKP).evaluate(VOL_DATA);
   double priceKM = FUNCTION.getPriceFunction(optionKM).evaluate(VOL_DATA);
   double derivativeK_FD = (priceKP - priceKM) / (2 * deltaK);
   assertEquals(derivativeK_FD, priceDerivative[2], 1E-7);
   // Derivative volatility.
   double deltaV = 0.0001;
   NormalFunctionData dataVP = new NormalFunctionData(F, DF, SIGMA + deltaV);
   NormalFunctionData dataVM = new NormalFunctionData(F, DF, SIGMA - deltaV);
   double priceVP = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataVP);
   double priceVM = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataVM);
   double derivativeV_FD = (priceVP - priceVM) / (2 * deltaV);
   assertEquals(derivativeV_FD, priceDerivative[1], 1E-6);
 }