public void testFdmMesherIntegral() { FdmMesherComposite mesher = new FdmMesherComposite( new Concentrating1dMesher(-1, 1.6, 21, new Pair <double?, double?>(0, 0.1)), new Concentrating1dMesher(-3, 4, 11, new Pair <double?, double?>(1, 0.01)), new Concentrating1dMesher(-2, 1, 5, new Pair <double?, double?>(0.5, 0.1))); FdmLinearOpLayout layout = mesher.layout(); Vector f = new Vector(mesher.layout().size()); for (FdmLinearOpIterator iter = layout.begin(); iter != layout.end(); ++iter) { double x = mesher.location(iter, 0); double y = mesher.location(iter, 1); double z = mesher.location(iter, 2); f[iter.index()] = x * x + 3 * y * y - 3 * z * z + 2 * x * y - x * z - 3 * y * z + 4 * x - y - 3 * z + 2; } double tol = 1e-12; // Simpson's rule has to be exact here, Mathematica code gives // Integrate[x*x+3*y*y-3*z*z+2*x*y-x*z-3*y*z+4*x-y-3*z+2, // {x, -1, 16/10}, {y, -3, 4}, {z, -2, 1}] double expectedSimpson = 876.512; double calculatedSimpson = new FdmMesherIntegral(mesher, new DiscreteSimpsonIntegral().value).integrate(f); if (Math.Abs(calculatedSimpson - expectedSimpson) > tol * expectedSimpson) { QAssert.Fail("discrete mesher integration using Simpson's rule failed: " + "\n calculated: " + calculatedSimpson + "\n expected: " + expectedSimpson); } double expectedTrapezoid = 917.0148209153263; double calculatedTrapezoid = new FdmMesherIntegral(mesher, new DiscreteTrapezoidIntegral().value).integrate(f); if (Math.Abs(calculatedTrapezoid - expectedTrapezoid) > tol * expectedTrapezoid) { QAssert.Fail("discrete mesher integration using Trapezoid rule failed: " + "\n calculated: " + calculatedTrapezoid + "\n expected: " + expectedTrapezoid); } }
public void testDerivativeWeightsOnNonUniformGrids() { Fdm1dMesher mesherX = new Concentrating1dMesher(-2.0, 3.0, 50, new Pair <double?, double?>(0.5, 0.01)); Fdm1dMesher mesherY = new Concentrating1dMesher(0.5, 5.0, 25, new Pair <double?, double?>(0.5, 0.1)); Fdm1dMesher mesherZ = new Concentrating1dMesher(-1.0, 2.0, 31, new Pair <double?, double?>(1.5, 0.01)); FdmMesher meshers = new FdmMesherComposite(mesherX, mesherY, mesherZ); FdmLinearOpLayout layout = meshers.layout(); FdmLinearOpIterator endIter = layout.end(); double tol = 1e-13; for (int direction = 0; direction < 3; ++direction) { SparseMatrix dfdx = new FirstDerivativeOp(direction, meshers).toMatrix(); SparseMatrix d2fdx2 = new SecondDerivativeOp(direction, meshers).toMatrix(); Vector gridPoints = meshers.locations(direction); for (FdmLinearOpIterator iter = layout.begin(); iter != endIter; ++iter) { int c = iter.coordinates()[direction]; int index = iter.index(); int indexM1 = layout.neighbourhood(iter, direction, -1); int indexP1 = layout.neighbourhood(iter, direction, +1); // test only if not on the boundary if (c == 0) { Vector twoPoints = new Vector(2); twoPoints[0] = 0.0; twoPoints[1] = gridPoints[indexP1] - gridPoints[index]; Vector ndWeights1st = new NumericalDifferentiation(x => x, 1, twoPoints).weights(); double beta1 = dfdx[index, index]; double gamma1 = dfdx[index, indexP1]; if (Math.Abs((beta1 - ndWeights1st[0]) / beta1) > tol || Math.Abs((gamma1 - ndWeights1st[1]) / gamma1) > tol) { QAssert.Fail("can not reproduce the weights of the " + "first order derivative operator " + "on the lower boundary" + "\n expected beta: " + ndWeights1st[0] + "\n calculated beta: " + beta1 + "\n difference beta: " + (beta1 - ndWeights1st[0]) + "\n expected gamma: " + ndWeights1st[1] + "\n calculated gamma: " + gamma1 + "\n difference gamma: " + (gamma1 - ndWeights1st[1])); } // free boundary condition by default double beta2 = d2fdx2[index, index]; double gamma2 = d2fdx2[index, indexP1]; if (Math.Abs(beta2) > Const.QL_EPSILON || Math.Abs(gamma2) > Const.QL_EPSILON) { QAssert.Fail("can not reproduce the weights of the " + "second order derivative operator " + "on the lower boundary" + "\n expected beta: " + 0.0 + "\n calculated beta: " + beta2 + "\n expected gamma: " + 0.0 + "\n calculated gamma: " + gamma2); } } else if (c == layout.dim()[direction] - 1) { Vector twoPoints = new Vector(2); twoPoints[0] = gridPoints[indexM1] - gridPoints[index]; twoPoints[1] = 0.0; Vector ndWeights1st = new NumericalDifferentiation(x => x, 1, twoPoints).weights(); double alpha1 = dfdx[index, indexM1]; double beta1 = dfdx[index, index]; if (Math.Abs((alpha1 - ndWeights1st[0]) / alpha1) > tol || Math.Abs((beta1 - ndWeights1st[1]) / beta1) > tol) { QAssert.Fail("can not reproduce the weights of the " + "first order derivative operator " + "on the upper boundary" + "\n expected alpha: " + ndWeights1st[0] + "\n calculated alpha: " + alpha1 + "\n difference alpha: " + (alpha1 - ndWeights1st[0]) + "\n expected beta: " + ndWeights1st[1] + "\n calculated beta: " + beta1 + "\n difference beta: " + (beta1 - ndWeights1st[1])); } // free boundary condition by default double alpha2 = d2fdx2[index, indexM1]; double beta2 = d2fdx2[index, index]; if (Math.Abs(alpha2) > Const.QL_EPSILON || Math.Abs(beta2) > Const.QL_EPSILON) { QAssert.Fail("can not reproduce the weights of the " + "second order derivative operator " + "on the upper boundary" + "\n expected alpha: " + 0.0 + "\n calculated alpha: " + alpha2 + "\n expected beta: " + 0.0 + "\n calculated beta: " + beta2); } } else { Vector threePoints = new Vector(3); threePoints[0] = gridPoints[indexM1] - gridPoints[index]; threePoints[1] = 0.0; threePoints[2] = gridPoints[indexP1] - gridPoints[index]; Vector ndWeights1st = new NumericalDifferentiation(x => x, 1, threePoints).weights(); double alpha1 = dfdx[index, indexM1]; double beta1 = dfdx[index, index]; double gamma1 = dfdx[index, indexP1]; if (Math.Abs((alpha1 - ndWeights1st[0]) / alpha1) > tol || Math.Abs((beta1 - ndWeights1st[1]) / beta1) > tol || Math.Abs((gamma1 - ndWeights1st[2]) / gamma1) > tol) { QAssert.Fail("can not reproduce the weights of the " + "first order derivative operator" + "\n expected alpha: " + ndWeights1st[0] + "\n calculated alpha: " + alpha1 + "\n difference alpha: " + (alpha1 - ndWeights1st[0]) + "\n expected beta: " + ndWeights1st[1] + "\n calculated beta: " + beta1 + "\n difference beta: " + (beta1 - ndWeights1st[1]) + "\n expected gamma: " + ndWeights1st[2] + "\n calculated gamma: " + gamma1 + "\n difference gamma: " + (gamma1 - ndWeights1st[2])); } Vector ndWeights2nd = new NumericalDifferentiation(x => x, 2, threePoints).weights(); double alpha2 = d2fdx2[index, indexM1]; double beta2 = d2fdx2[index, index]; double gamma2 = d2fdx2[index, indexP1]; if (Math.Abs((alpha2 - ndWeights2nd[0]) / alpha2) > tol || Math.Abs((beta2 - ndWeights2nd[1]) / beta2) > tol || Math.Abs((gamma2 - ndWeights2nd[2]) / gamma2) > tol) { QAssert.Fail("can not reproduce the weights of the " + "second order derivative operator" + "\n expected alpha: " + ndWeights2nd[0] + "\n calculated alpha: " + alpha2 + "\n difference alpha: " + (alpha2 - ndWeights2nd[0]) + "\n expected beta: " + ndWeights2nd[1] + "\n calculated beta: " + beta2 + "\n difference beta: " + (beta2 - ndWeights2nd[1]) + "\n expected gamma: " + ndWeights2nd[2] + "\n calculated gamma: " + gamma2 + "\n difference gamma: " + (gamma2 - ndWeights2nd[2])); } } } } }
static void Main(string[] args) { const int xSteps = 100; const int tSteps = 25; const int dampingSteps = 0; Date today = new Date(15, Month.January, 2020); Settings.instance().setEvaluationDate(today); DayCounter dc = new Actual365Fixed(); YieldTermStructureHandle rTS = new YieldTermStructureHandle( new FlatForward(today, 0.06, dc)); YieldTermStructureHandle qTS = new YieldTermStructureHandle( new FlatForward(today, 0.02, dc)); const double strike = 110.0; StrikedTypePayoff payoff = new PlainVanillaPayoff(Option.Type.Put, strike); Date maturityDate = today.Add(new Period(1, TimeUnit.Years)); double maturity = dc.yearFraction(today, maturityDate); Exercise exercise = new AmericanExercise(today, maturityDate); Instrument vanillaOption = new VanillaOption(payoff, exercise); QuoteHandle spot = new QuoteHandle(new SimpleQuote(100.0)); BlackVolTermStructureHandle volatility = new BlackVolTermStructureHandle( new BlackConstantVol(today, new TARGET(), 0.20, dc)); BlackScholesMertonProcess process = new BlackScholesMertonProcess(spot, qTS, rTS, volatility); vanillaOption.setPricingEngine(new FdBlackScholesVanillaEngine( process, tSteps, xSteps, dampingSteps)); double expected = vanillaOption.NPV(); // build an PDE engine from scratch Fdm1dMesher equityMesher = new FdmBlackScholesMesher( xSteps, process, maturity, strike, nullDouble(), nullDouble(), 0.0001, 1.5, new DoublePair(strike, 0.1)); FdmMesherComposite mesher = new FdmMesherComposite(equityMesher); FdmLinearOpComposite op = new FdmBlackScholesOp(mesher, process, strike); FdmInnerValueCalculator calc = new FdmLogInnerValue(payoff, mesher, 0); QlArray x = new QlArray(equityMesher.size()); QlArray rhs = new QlArray(equityMesher.size()); FdmLinearOpIterator iter = mesher.layout().begin(); for (uint i = 0; i < rhs.size(); ++i, iter.increment()) { x.set(i, mesher.location(iter, 0)); rhs.set(i, calc.avgInnerValue(iter, maturity)); } FdmBoundaryConditionSet bcSet = new FdmBoundaryConditionSet(); FdmStepConditionComposite stepCondition = FdmStepConditionComposite.vanillaComposite( new DividendSchedule(), exercise, mesher, calc, today, dc); FdmLinearOpComposite proxyOp = new FdmLinearOpCompositeProxy( new FdmBSDelegate(op)); FdmBackwardSolver solver = new FdmBackwardSolver( proxyOp, bcSet, stepCondition, FdmSchemeDesc.Douglas()); solver.rollback(rhs, maturity, 0.0, tSteps, dampingSteps); double logS = Math.Log(spot.value()); double calculated = new CubicNaturalSpline(x, rhs).call(logS); Console.WriteLine("Homebrew PDE engine : {0:0.0000}", calculated); Console.WriteLine("FdBlackScholesVanillaEngine: {0:0.0000}", expected); }