/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2003, 2004 Ferdinando Ametrano Copyright (C) 2003 StatPro Italia srl Copyright (C) 2005 Gary Kennedy This file is part of QuantLib, a free-software/open-source library for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email . The license is also available online at . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. */ #include "distributions.hpp" #include "utilities.hpp" #include #include #include #include using namespace QuantLib; using namespace boost::unit_test_framework; QL_BEGIN_TEST_LOCALS(DistributionTest) Real average = 1.0, sigma = 2.0; Real gaussian(Real x) { Real normFact = sigma*std::sqrt(2*M_PI); Real dx = x-average; return std::exp(-dx*dx/(2.0*sigma*sigma))/normFact; } Real gaussianDerivative(Real x) { Real normFact = sigma*sigma*sigma*std::sqrt(2*M_PI); Real dx = x-average; return -dx*std::exp(-dx*dx/(2.0*sigma*sigma))/normFact; } struct BivariateTestData { Real a; Real b; Real rho; Real result; }; template void checkBivariate(const char* tag) { BivariateTestData values[] = { /* The data below are from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag 193 */ { 0.0, 0.0, 0.0, 0.250000 }, { 0.0, 0.0, -0.5, 0.166667 }, { 0.0, 0.0, 0.5, 1.0/3 }, { 0.0, -0.5, 0.0, 0.154269 }, { 0.0, -0.5, -0.5, 0.081660 }, { 0.0, -0.5, 0.5, 0.226878 }, { 0.0, 0.5, 0.0, 0.345731 }, { 0.0, 0.5, -0.5, 0.273122 }, { 0.0, 0.5, 0.5, 0.418340 }, { -0.5, 0.0, 0.0, 0.154269 }, { -0.5, 0.0, -0.5, 0.081660 }, { -0.5, 0.0, 0.5, 0.226878 }, { -0.5, -0.5, 0.0, 0.095195 }, { -0.5, -0.5, -0.5, 0.036298 }, { -0.5, -0.5, 0.5, 0.163319 }, { -0.5, 0.5, 0.0, 0.213342 }, { -0.5, 0.5, -0.5, 0.145218 }, { -0.5, 0.5, 0.5, 0.272239 }, { 0.5, 0.0, 0.0, 0.345731 }, { 0.5, 0.0, -0.5, 0.273122 }, { 0.5, 0.0, 0.5, 0.418340 }, { 0.5, -0.5, 0.0, 0.213342 }, { 0.5, -0.5, -0.5, 0.145218 }, { 0.5, -0.5, 0.5, 0.272239 }, { 0.5, 0.5, 0.0, 0.478120 }, { 0.5, 0.5, -0.5, 0.419223 }, { 0.5, 0.5, 0.5, 0.546244 }, // known analytical values { 0.0, 0.0, std::sqrt(1/2.0), 3.0/8}, // { 0.0, big, any, 0.500000 }, { 0.0, 30, -1.0, 0.500000 }, { 0.0, 30, 0.0, 0.500000 }, { 0.0, 30, 1.0, 0.500000 }, // { big, big, any, 1.000000 }, { 30, 30, -1.0, 1.000000 }, { 30, 30, 0.0, 1.000000 }, { 30, 30, 1.0, 1.000000 }, // {-big, any, any, 0.000000 } { -30, -1.0, -1.0, 0.000000 }, { -30, 0.0, -1.0, 0.000000 }, { -30, 1.0, -1.0, 0.000000 }, { -30, -1.0, 0.0, 0.000000 }, { -30, 0.0, 0.0, 0.000000 }, { -30, 1.0, 0.0, 0.000000 }, { -30, -1.0, 1.0, 0.000000 }, { -30, 0.0, 1.0, 0.000000 }, { -30, 1.0, 1.0, 0.000000 } }; for (Size i=0; i= tolerance) { BOOST_ERROR(tag << " bivariate cumulative distribution\n" << " case: " << i+1 << "\n" << QL_FIXED << " a: " << values[i].a << "\n" << " b: " << values[i].b << "\n" << " rho: " << values[i].rho <<"\n" << QL_SCIENTIFIC << " tabulated value: " << values[i].result << "\n" << " result: " << value); } } } template void checkBivariateAtZero(const char* tag, Real tolerance) { /* BVN(0.0,0.0,rho) = 1/4 + arcsin(rho)/(2*M_PI) "Handbook of the Normal Distribution", J.K. Patel & C.B.Read, 2nd Ed, 1996 */ const Real rho[] = { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99999 }; const Real x(0.0); const Real y(0.0); for (Size i=0;i=tolerance) { BOOST_ERROR(tag << " bivariate cumulative distribution\n" << QL_SCIENTIFIC << " rho: " << sgn*rho[i] << "\n" << " expected: " << expected << "\n" << " realised: " << realised << "\n" << " tolerance: " << tolerance); } } } } QL_END_TEST_LOCALS(DistributionTest) void DistributionTest::testNormal() { BOOST_MESSAGE("Testing normal distributions..."); InverseCumulativeNormal invCumStandardNormal; Real check = invCumStandardNormal(0.5); if (check != 0.0e0) { BOOST_ERROR("C++ inverse cumulative of the standard normal at 0.5 is " << QL_SCIENTIFIC << check << "\n instead of zero: something is wrong!"); } NormalDistribution normal(average,sigma); CumulativeNormalDistribution cum(average,sigma); InverseCumulativeNormal invCum(average,sigma); Size numberOfStandardDeviation = 6; Real xMin = average - numberOfStandardDeviation*sigma, xMax = average + numberOfStandardDeviation*sigma; Size N = 100001; Real h = (xMax-xMin)/(N-1); std::vector x(N), y(N), yd(N), temp(N), diff(N); Size i; for (i=0; i()); Real e = norm(diff.begin(),diff.end(),h); if (e > 1.0e-16) { BOOST_ERROR("norm of C++ NormalDistribution minus analytic Gaussian: " << QL_SCIENTIFIC << e << "\n" << "tolerance exceeded"); } // check that invCum . cum = identity std::transform(x.begin(),x.end(),temp.begin(),cum); std::transform(temp.begin(),temp.end(),temp.begin(),invCum); std::transform(x.begin(),x.end(),temp.begin(),diff.begin(), std::minus()); e = norm(diff.begin(),diff.end(),h); if (e > 1.0e-7) { BOOST_ERROR("norm of invCum . cum minus identity: " << QL_SCIENTIFIC << e << "\n" << "tolerance exceeded"); } // check that cum.derivative = Gaussian for (i=0; i()); e = norm(diff.begin(),diff.end(),h); if (e > 1.0e-16) { BOOST_ERROR( "norm of C++ Cumulative.derivative minus analytic Gaussian: " << QL_SCIENTIFIC << e << "\n" << "tolerance exceeded"); } // check that normal.derivative = gaussianDerivative for (i=0; i()); e = norm(diff.begin(),diff.end(),h); if (e > 1.0e-16) { BOOST_ERROR("norm of C++ Normal.derivative minus analytic derivative: " << QL_SCIENTIFIC << e << "\n" << "tolerance exceeded"); } } void DistributionTest::testBivariate() { BOOST_MESSAGE("Testing bivariate cumulative normal distribution..."); checkBivariateAtZero( "Drezner 1978", 1.0e-6); checkBivariate("Drezner 1978"); checkBivariateAtZero( "West 2004", 1.0e-15); checkBivariate("West 2004"); } void DistributionTest::testPoisson() { BOOST_MESSAGE("Testing Poisson distribution..."); for (Real mean=0.0; mean<=10.0; mean+=0.5) { BigNatural i = 0; PoissonDistribution pdf(mean); Real calculated = pdf(i); Real logHelper = -mean; Real expected = std::exp(logHelper); Real error = std::fabs(calculated-expected); if (error > 1.0e-16) BOOST_ERROR("Poisson pdf(" << mean << ")(" << i << ")\n" << std::setprecision(16) << " calculated: " << calculated << "\n" << " expected: " << expected << "\n" << " error: " << error); for (i=1; i<25; i++) { calculated = pdf(i); if (mean == 0.0) { expected = 0.0; } else { logHelper = logHelper+std::log(mean)-std::log(Real(i)); expected = std::exp(logHelper); } error = std::fabs(calculated-expected); if (error>1.0e-13) BOOST_ERROR("Poisson pdf(" << mean << ")(" << i << ")\n" << std::setprecision(13) << " calculated: " << calculated << "\n" << " expected: " << expected << "\n" << " error: " << error); } } } void DistributionTest::testCumulativePoisson() { BOOST_MESSAGE("Testing cumulative Poisson distribution..."); for (Real mean=0.0; mean<=10.0; mean+=0.5) { BigNatural i = 0; CumulativePoissonDistribution cdf(mean); Real cumCalculated = cdf(i); Real logHelper = -mean; Real cumExpected = std::exp(logHelper); Real error = std::fabs(cumCalculated-cumExpected); if (error>1.0e-13) BOOST_ERROR("Poisson cdf(" << mean << ")(" << i << ")\n" << std::setprecision(13) << " calculated: " << cumCalculated << "\n" << " expected: " << cumExpected << "\n" << " error: " << error); for (i=1; i<25; i++) { cumCalculated = cdf(i); if (mean == 0.0) { cumExpected = 1.0; } else { logHelper = logHelper+std::log(mean)-std::log(Real(i)); cumExpected += std::exp(logHelper); } error = std::fabs(cumCalculated-cumExpected); if (error>1.0e-12) BOOST_ERROR("Poisson cdf(" << mean << ")(" << i << ")\n" << std::setprecision(12) << " calculated: " << cumCalculated << "\n" << " expected: " << cumExpected << "\n" << " error: " << error); } } } void DistributionTest::testInverseCumulativePoisson() { BOOST_MESSAGE("Testing inverse cumulative Poisson distribution..."); InverseCumulativePoisson icp(1.0); Real data[] = { 0.2, 0.5, 0.9, 0.98, 0.99, 0.999, 0.9999, 0.99995, 0.99999, 0.999999, 0.9999999, 0.99999999 }; for (Size i=0; iadd(BOOST_TEST_CASE(&DistributionTest::testNormal)); suite->add(BOOST_TEST_CASE(&DistributionTest::testBivariate)); suite->add(BOOST_TEST_CASE(&DistributionTest::testPoisson)); suite->add(BOOST_TEST_CASE(&DistributionTest::testCumulativePoisson)); suite->add( BOOST_TEST_CASE(&DistributionTest::testInverseCumulativePoisson)); return suite; }