#include"fdmlinearop.hpp"#include"utilities.hpp"#include<ql/quotes/simplequote.hpp>#include<ql/time/daycounters/actual360.hpp>#include<ql/time/daycounters/actual365fixed.hpp>#include<ql/processes/hestonprocess.hpp>#include<ql/processes/hullwhiteprocess.hpp>#include<ql/processes/blackscholesprocess.hpp>#include<ql/processes/hybridhestonhullwhiteprocess.hpp>#include<ql/math/interpolations/bilinearinterpolation.hpp>#include<ql/math/interpolations/bicubicsplineinterpolation.hpp>#include<ql/math/interpolations/cubicinterpolation.hpp>#include<ql/math/integrals/discreteintegrals.hpp>#include<ql/math/randomnumbers/rngtraits.hpp>#include<ql/models/equity/hestonmodel.hpp>#include<ql/termstructures/yield/zerocurve.hpp>#include<ql/pricingengines/vanilla/analyticeuropeanengine.hpp>#include<ql/pricingengines/vanilla/mchestonhullwhiteengine.hpp>#include<ql/methods/finitedifferences/finitedifferencemodel.hpp>#include<ql/math/matrixutilities/gmres.hpp>#include<ql/math/matrixutilities/bicgstab.hpp>#include<ql/methods/finitedifferences/schemes/douglasscheme.hpp>#include<ql/methods/finitedifferences/schemes/hundsdorferscheme.hpp>#include<ql/methods/finitedifferences/schemes/craigsneydscheme.hpp>#include<ql/methods/finitedifferences/meshers/uniformgridmesher.hpp>#include<ql/methods/finitedifferences/meshers/uniform1dmesher.hpp>#include<ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp>#include<ql/methods/finitedifferences/meshers/fdmblackscholesmesher.hpp>#include<ql/methods/finitedifferences/solvers/fdmbackwardsolver.hpp>#include<ql/methods/finitedifferences/operators/fdmblackscholesop.hpp>#include<ql/methods/finitedifferences/utilities/fdmmesherintegral.hpp>#include<ql/methods/finitedifferences/utilities/fdminnervaluecalculator.hpp>#include<ql/methods/finitedifferences/operators/numericaldifferentiation.hpp>#include<ql/methods/finitedifferences/operators/fdmlinearop.hpp>#include<ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>#include<ql/methods/finitedifferences/operators/fdmlinearopcomposite.hpp>#include<ql/methods/finitedifferences/operators/fdmhestonhullwhiteop.hpp>#include<ql/methods/finitedifferences/meshers/fdmhestonvariancemesher.hpp>#include<ql/methods/finitedifferences/operators/fdmhestonop.hpp>#include<ql/methods/finitedifferences/solvers/fdmhestonsolver.hpp>#include<ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>#include<ql/methods/finitedifferences/solvers/fdmndimsolver.hpp>#include<ql/methods/finitedifferences/solvers/fdm3dimsolver.hpp>#include<ql/methods/finitedifferences/stepconditions/fdmamericanstepcondition.hpp>#include<ql/methods/finitedifferences/stepconditions/fdmstepconditioncomposite.hpp>#include<ql/methods/finitedifferences/utilities/fdmdividendhandler.hpp>#include<ql/methods/finitedifferences/operators/firstderivativeop.hpp>#include<ql/methods/finitedifferences/operators/secondderivativeop.hpp>#include<ql/methods/finitedifferences/operators/secondordermixedderivativeop.hpp>#include<ql/math/matrixutilities/sparseilupreconditioner.hpp>#include<ql/functional.hpp>#include<boost/numeric/ublas/vector.hpp>#include<boost/numeric/ublas/operation.hpp>#include<numeric>#include<utility>usingnamespace QuantLib;usingnamespace boost::unit_test_framework;namespace{
classFdmHestonExpressCondition:publicStepCondition<Array>{
public:FdmHestonExpressCondition(std::vector<Real> redemptions,
std::vector<Real> triggerLevels,
std::vector<Time> exerciseTimes,
ext::shared_ptr<FdmMesher> mesher):redemptions_(std::move(redemptions)),triggerLevels_(std::move(triggerLevels)),exerciseTimes_(std::move(exerciseTimes)),mesher_(std::move(mesher)){
}voidapplyTo(Array& a, Time t)constoverride{
auto iter = std::find(exerciseTimes_.begin(), exerciseTimes_.end(), t);if(iter != exerciseTimes_.end()){
Size index = std::distance(exerciseTimes_.begin(), iter);
ext::shared_ptr<FdmLinearOpLayout> layout = mesher_->layout();const FdmLinearOpIterator endIter = layout->end();for(FdmLinearOpIterator iter = layout->begin();
iter != endIter;++iter){
const Real s = std::exp(mesher_->location(iter,0));if(s > triggerLevels_[index]){
a[iter.index()]= redemptions_[index];}}}}private:const std::vector<Real> redemptions_;const std::vector<Real> triggerLevels_;const std::vector<Time> exerciseTimes_;const ext::shared_ptr<FdmMesher> mesher_;};classExpressPayoff:publicPayoff{
public:
std::string name()constoverride{
return"ExpressPayoff";}
std::string description()constoverride{
return"ExpressPayoff";}
Real operator()(Real s)constoverride{
return((s >=100.0)?108.0:100.0)-((s <=75.0)?Real(100.0- s):0.0);}};template<classT,classU,classV>structmultiplies{
V operator()(T t, U u){
return t*u;}};}voidFdmLinearOpTest::testFdmLinearOpLayout(){
BOOST_TEST_MESSAGE("Testing indexing of a linear operator...");const std::vector<Size> dim ={
5,7,8};
FdmLinearOpLayout layout =FdmLinearOpLayout(dim);
Size calculatedDim = layout.dim().size();
Size expectedDim = dim.size();if(calculatedDim != expectedDim){
BOOST_ERROR("index.dimensions() should be "<< expectedDim
<<", but is "<< calculatedDim);}
Size calculatedSize = layout.size();
Size expectedSize = std::accumulate(dim.begin(), dim.end(),1, std::multiplies<>());if(calculatedSize != expectedSize){
BOOST_FAIL("index.size() should be "<< expectedSize <<", but is "<< calculatedSize);}for(Size k=0; k < dim[0];++k){
for(Size l=0; l < dim[1];++l){
for(Size m=0; m < dim[2];++m){
std::vector<Size>tmp(3);
tmp[0]= k; tmp[1]= l; tmp[2]= m;
Size calculatedIndex = layout.index(tmp);
Size expectedIndex = k + l*dim[0]+ m*dim[0]*dim[1];if(expectedIndex != layout.index(tmp)){
BOOST_FAIL("index.size() should be "<< expectedIndex
<<", but is "<< calculatedIndex);}}}}
FdmLinearOpIterator iter = layout.begin();for(Size m=0; m < dim[2];++m){
for(Size l=0; l < dim[1];++l){
for(Size k=0; k < dim[0];++k,++iter){
for(Size n=1; n <4;++n){
Size nn = layout.neighbourhood(iter,1, n);
Size calculatedIndex = k + m*dim[0]*dim[1]+((l < dim[1]-n)? l+n
: dim[1]-1-(l+n-(dim[1]-1)))*dim[0];if(nn != calculatedIndex){
BOOST_FAIL("next neighbourhood index is "<< nn
<<" but should be "<< calculatedIndex);}}for(Size n=1; n <7;++n){
Size nn = layout.neighbourhood(iter,2,-Integer(n));
Size calculatedIndex = k + l*dim[0]+((m < n)? n-m : m-n)*dim[0]*dim[1];if(nn != calculatedIndex){
BOOST_FAIL("next neighbourhood index is "<< nn
<<" but should be "<< calculatedIndex);}}}}}}voidFdmLinearOpTest::testUniformGridMesher(){
BOOST_TEST_MESSAGE("Testing uniform grid mesher...");const std::vector<Size> dim ={
5,7,8};
ext::shared_ptr<FdmLinearOpLayout>layout(newFdmLinearOpLayout(dim));
std::vector<std::pair<Real, Real>> boundaries ={
{
-5,10},{
5,100},{
10,20}};
UniformGridMesher mesher(layout, boundaries);const Real dx1 =15.0/(dim[0]-1);const Real dx2 =95.0/(dim[1]-1);const Real dx3 =10.0/(dim[2]-1);constexprdouble tol =100*QL_EPSILON;if( std::fabs(dx1-mesher.dminus(layout->begin(),0))> tol
|| std::fabs(dx1-mesher.dplus(layout->begin(),0))> tol
|| std::fabs(dx2-mesher.dminus(layout->begin(),1))> tol
|| std::fabs(dx2-mesher.dplus(layout->begin(),1))> tol
|| std::fabs(dx3-mesher.dminus(layout->begin(),2))> tol
|| std::fabs(dx3-mesher.dplus(layout->begin(),2))> tol ){
BOOST_FAIL("inconsistent uniform mesher object");}}voidFdmLinearOpTest::testFirstDerivativesMapApply(){
BOOST_TEST_MESSAGE("Testing application of first-derivatives map...");const std::vector<Size> dim ={
400,100,50};
ext::shared_ptr<FdmLinearOpLayout>index(newFdmLinearOpLayout(dim));
std::vector<std::pair<Real, Real>> boundaries ={
{
-5,5},{
0,10},{
5,15}};
ext::shared_ptr<FdmMesher>mesher(newUniformGridMesher(index, boundaries));
FirstDerivativeOp map(2, mesher);
Array r(mesher->layout()->size());const FdmLinearOpIterator endIter = index->end();for(FdmLinearOpIterator iter = index->begin(); iter != endIter;++iter){
r[iter.index()]= std::sin(mesher->location(iter,0))+ std::cos(mesher->location(iter,2));}
Array t = map.apply(r);const Real dz =(boundaries[2].second-boundaries[2].first)/(dim[2]-1);for(FdmLinearOpIterator iter = index->begin(); iter != endIter;++iter){
const Size z = iter.coordinates()[2];const Size z0 =(z >0)? z-1:1;const Size z2 =(z < dim[2]-1)? z+1: dim[2]-2;const Real lz0 = boundaries[2].first + z0*dz;const Real lz2 = boundaries[2].first + z2*dz;
Real expected;if(z ==0){
expected =(std::cos(boundaries[2].first+dz)- std::cos(boundaries[2].first))/dz;}elseif(z == dim[2]-1){
expected =(std::cos(boundaries[2].second)- std::cos(boundaries[2].second-dz))/dz;}else{
expected =(std::cos(lz2)-std::cos(lz0))/(2*dz);}const Real calculated = t[iter.index()];if(std::fabs(calculated - expected)>1e-10){
BOOST_FAIL("first derivative calculation failed."<<"\n calculated: "<< calculated
<<"\n expected: "<< expected);}}}voidFdmLinearOpTest::testSecondDerivativesMapApply(){
BOOST_TEST_MESSAGE("Testing application of second-derivatives map...");const std::vector<Size> dim ={
50,50,50};
ext::shared_ptr<FdmLinearOpLayout>index(newFdmLinearOpLayout(dim));
std::vector<std::pair<Real, Real>> boundaries ={
{
0,0.5},{
0,0.5},{
0,0.5}};
ext::shared_ptr<FdmMesher>mesher(newUniformGridMesher(index, boundaries));
Array r(mesher->layout()->size());const FdmLinearOpIterator endIter = index->end();for(FdmLinearOpIterator iter = index->begin(); iter != endIter;++iter){
const Real x = mesher->location(iter,0);const Real y = mesher->location(iter,1);const Real z = mesher->location(iter,2);
r[iter.index()]= std::sin(x)*std::cos(y)*std::exp(z);}
Array t =SecondDerivativeOp(0, mesher).apply(r);const Real tol =5e-2;for(FdmLinearOpIterator iter = index->begin(); iter != endIter;++iter){
const Size i = iter.index();const Real x = mesher->location(iter,0);const Real y = mesher->location(iter,1);const Real z = mesher->location(iter,2);
Real d =-std::sin(x)*std::cos(y)*std::exp(z);if(iter.coordinates()[0]==0|| iter.coordinates()[0]== dim[0]-1){
d =0;}if(std::fabs(d - t[i])> tol){
BOOST_FAIL("numerical derivative in dx^2 deviation is too big"<<"\n found at "<< x <<" "<< y <<" "<< z);}}
t =SecondDerivativeOp(1, mesher).apply(r);for(FdmLinearOpIterator iter = index->begin(); iter != endIter;++iter){
const Size i = iter.index();const Real x = mesher->location(iter,0);const Real y = mesher->location(iter,1);const Real z = mesher->location(iter,2);
Real d =-std::sin(x)*std::cos(y)*std::exp(z);if(iter.coordinates()[1]==0|| iter.coordinates()[1]== dim[1]-1){
d =0;}if(std::fabs(d - t[i])> tol){
BOOST_FAIL("numerical derivative in dy^2 deviation is too big"<<"\n found at "<< x <<" "<< y <<