private double logContactPriceFromPureSpot(final LocalVolatilitySurfaceMoneyness lv) {

    final double fT = DIV_CURVES.getF(EXPIRY);
    final double dT = DIV_CURVES.getD(EXPIRY);

    final double dStar = dT / (fT - dT);

    final ConvectionDiffusionPDE1DCoefficients pde =
        PDE_PROVIDER.getLogBackwardsLocalVol(EXPIRY, lv);

    final double theta = 0.5;
    final double yL = -0.5;
    final double yH = 0.5;
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower =
        new NeumannBoundaryCondition(1 / (1 + dStar * Math.exp(-yL)), yL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, yH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, EXPIRY, 100, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db =
        new PDE1DDataBundle<>(pde, PURE_LOG_PAY_OFF, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final int n = res.getNumberSpaceNodes();

    final double val = res.getFunctionValue(n / 2);
    return val;
  }
  public void testMixedLogNormalVolSurface() {

    final double[] weights = new double[] {0.9, 0.1};
    final double[] sigmas = new double[] {0.2, 0.8};
    final MultiHorizonMixedLogNormalModelData data =
        new MultiHorizonMixedLogNormalModelData(weights, sigmas);
    final LocalVolatilitySurfaceStrike lv =
        MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(FORWARD_CURVE, data);
    final LocalVolatilitySurfaceMoneyness lvm =
        LocalVolatilitySurfaceConverter.toMoneynessSurface(lv, FORWARD_CURVE);
    final double expected =
        Math.sqrt(weights[0] * sigmas[0] * sigmas[0] + weights[1] * sigmas[1] * sigmas[1]);

    final double ft = FORWARD_CURVE.getForward(EXPIRY);
    final double theta = 0.5;
    // Review the accuracy is very dependent on these numbers
    final double fL = Math.log(ft / 30);
    final double fH = Math.log(30 * ft);

    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, fL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, fH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 50, 0.0);
    final MeshingFunction spaceMesh = new HyperbolicMeshing(fL, fH, (fL + fH) / 2, 101, 0.4);

    final ConvectionDiffusionPDE1DStandardCoefficients pde =
        PDE_DATA_PROVIDER.getLogBackwardsLocalVol(EXPIRY, lvm);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db =
        new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(
            pde, INITIAL_COND, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final int n = res.getNumberSpaceNodes();
    final double[] values = new double[n];
    for (int i = 0; i < n; i++) {

      values[i] = res.getFunctionValue(i);
    }

    final Interpolator1DDataBundle idb = INTERPOLATOR.getDataBundle(grid.getSpaceNodes(), values);
    final double elogS = INTERPOLATOR.interpolate(idb, Math.log(ft));
    final double kVol = Math.sqrt(-2 * (elogS - Math.log(ft)) / EXPIRY);

    assertEquals(
        expected, kVol,
        1e-3); // TODO Improve on 10bps error - local surface is (by construction) very smooth.
               // NOTE: this has got worse since we improved the T -> 0
    // behaviour of the mixed log-normal local volatility surface
  }
  public void testFlatSurface() {
    final double theta = 0.5;
    final double ft = FORWARD_CURVE.getForward(EXPIRY);

    final double fL = Math.log(ft / 5.0);
    final double fH = Math.log(5.0 * ft);
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, fL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, fH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 100, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(fL, fH, 101, 0.0);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db =
        new PDE1DDataBundle<>(PDE, INITIAL_COND, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final int n = res.getNumberSpaceNodes();

    final double kVol = Math.sqrt(-2 * (res.getFunctionValue(n / 2) - Math.log(ft)) / EXPIRY);

    assertEquals(FLAT_VOL, kVol, 1e-6);

    final YieldAndDiscountCurve yieldCurve =
        new YieldCurve("test", ConstantDoublesCurve.from(DRIFT));
    final AffineDividends ad = AffineDividends.noDividends();

    final EquityVarianceSwapBackwardsPurePDE backSolver = new EquityVarianceSwapBackwardsPurePDE();
    final PureLocalVolatilitySurface plv =
        new PureLocalVolatilitySurface(ConstantDoublesSurface.from(FLAT_VOL));

    final double[] res2 = backSolver.expectedVariance(SPOT, yieldCurve, ad, EXPIRY, plv);
    final double kVol2 = Math.sqrt(res2[0] / EXPIRY);
    assertEquals(FLAT_VOL, kVol2, 1e-6);
  }