Coverage Report

Created: 2026-03-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/math/distributions/bivariatenormaldistribution.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2002, 2003 Ferdinando Ametrano
5
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
6
 Copyright (C) 2003 StatPro Italia srl
7
 Copyright (C) 2005 Gary Kennedy
8
9
 This file is part of QuantLib, a free-software/open-source library
10
 for financial quantitative analysts and developers - http://quantlib.org/
11
12
 QuantLib is free software: you can redistribute it and/or modify it
13
 under the terms of the QuantLib license.  You should have received a
14
 copy of the license along with this program; if not, please email
15
 <quantlib-dev@lists.sf.net>. The license is also available online at
16
 <https://www.quantlib.org/license.shtml>.
17
18
 This program is distributed in the hope that it will be useful, but WITHOUT
19
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 FOR A PARTICULAR PURPOSE.  See the license for more details.
21
*/
22
23
#include <ql/math/distributions/bivariatenormaldistribution.hpp>
24
#include <ql/math/integrals/gaussianquadratures.hpp>
25
26
namespace QuantLib {
27
28
    // Drezner 1978
29
30
    const Real BivariateCumulativeNormalDistributionDr78::x_[] = {
31
        0.24840615,
32
        0.39233107,
33
        0.21141819,
34
        0.03324666,
35
        0.00082485334
36
    };
37
38
    const Real BivariateCumulativeNormalDistributionDr78::y_[] = {
39
        0.10024215,
40
        0.48281397,
41
        1.06094980,
42
        1.77972940,
43
        2.66976040000
44
    };
45
46
    BivariateCumulativeNormalDistributionDr78::
47
    BivariateCumulativeNormalDistributionDr78(Real rho)
48
0
    : rho_(rho), rho2_(rho*rho) {
49
50
0
        QL_REQUIRE(rho>=-1.0,
51
0
                   "rho must be >= -1.0 (" << rho << " not allowed)");
52
0
        QL_REQUIRE(rho<=1.0,
53
0
                   "rho must be <= 1.0 (" << rho << " not allowed)");
54
0
    }
55
56
    Real BivariateCumulativeNormalDistributionDr78::operator()(Real a,
57
0
                                                               Real b) const {
58
59
0
        CumulativeNormalDistribution cumNormalDist;
60
0
        Real CumNormDistA = cumNormalDist(a);
61
0
        Real CumNormDistB = cumNormalDist(b);
62
0
        Real MaxCumNormDistAB = std::max(CumNormDistA, CumNormDistB);
63
0
        Real MinCumNormDistAB = std::min(CumNormDistA, CumNormDistB);
64
65
0
        if (1.0-MaxCumNormDistAB<1e-15)
66
0
            return MinCumNormDistAB;
67
68
0
        if (MinCumNormDistAB<1e-15)
69
0
            return MinCumNormDistAB;
70
71
0
        Real a1 = a / std::sqrt(2.0 * (1.0 - rho2_));
72
0
        Real b1 = b / std::sqrt(2.0 * (1.0 - rho2_));
73
74
0
        Real result=-1.0;
75
76
0
        if (a<=0.0 && b<=0 && rho_<=0) {
77
0
            Real sum=0.0;
78
0
            for (Size i=0; i<5; i++) {
79
0
                for (Size j=0;j<5; j++) {
80
0
                    sum += x_[i]*x_[j]*
81
0
                        std::exp(a1*(2.0*y_[i]-a1)+b1*(2.0*y_[j]-b1)
82
0
                                 +2.0*rho_*(y_[i]-a1)*(y_[j]-b1));
83
0
                }
84
0
            }
85
0
            result = std::sqrt(1.0 - rho2_)/M_PI*sum;
86
0
        } else if (a<=0 && b>=0 && rho_>=0) {
87
0
            BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(-rho_);
88
0
            result= CumNormDistA - bivCumNormalDist(a, -b);
89
0
        } else if (a>=0.0 && b<=0.0 && rho_>=0.0) {
90
0
            BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(-rho_);
91
0
            result= CumNormDistB - bivCumNormalDist(-a, b);
92
0
        } else if (a>=0.0 && b>=0.0 && rho_<=0.0) {
93
0
            result= CumNormDistA + CumNormDistB -1.0 + (*this)(-a, -b);
94
0
        } else if (a*b*rho_>0.0) {
95
0
            Real rho1 = (rho_*a-b)*(a>0.0 ? 1.0: -1.0)/
96
0
                std::sqrt(a*a-2.0*rho_*a*b+b*b);
97
0
            BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(rho1);
98
99
0
            Real rho2 = (rho_*b-a)*(b>0.0 ? 1.0: -1.0)/
100
0
                std::sqrt(a*a-2.0*rho_*a*b+b*b);
101
0
            BivariateCumulativeNormalDistributionDr78 CBND2(rho2);
102
103
0
            Real delta = (1.0-(a>0.0 ? 1.0: -1.0)*(b>0.0 ? 1.0: -1.0))/4.0;
104
105
0
            result= bivCumNormalDist(a, 0.0) + CBND2(b, 0.0) - delta;
106
0
        } else {
107
0
            QL_FAIL("case not handled");
108
0
        }
109
110
0
        return result;
111
0
    }
112
113
114
    // West 2004
115
116
    namespace {
117
118
        class eqn3 { /* Relates to eqn3 Genz 2004 */
119
          public:
120
            eqn3(Real h, Real k, Real asr)
121
0
            : hk_(h * k), asr_(asr), hs_((h * h + k * k) / 2) {}
122
123
0
            Real operator()(Real x) const {
124
0
                Real sn = std::sin(asr_ * (-x + 1) * 0.5);
125
0
                return std::exp((sn * hk_ - hs_) / (1.0 - sn * sn));
126
0
            }
127
          private:
128
            Real hk_, asr_, hs_;
129
        };
130
131
        class eqn6 { /* Relates to eqn6 Genz 2004 */
132
          public:
133
            eqn6(Real a, Real c, Real d, Real bs, Real hk)
134
0
            : a_(a), c_(c), d_(d), bs_(bs), hk_(hk) {}
135
0
            Real operator()(Real x) const {
136
0
                Real xs = a_ * (-x + 1);
137
0
                xs = std::fabs(xs*xs);
138
0
                Real rs = std::sqrt(1 - xs);
139
0
                Real asr = -(bs_ / xs + hk_) / 2;
140
0
                if (asr > -100.0) {
141
0
                    return (a_ * std::exp(asr) *
142
0
                            (std::exp(-hk_ * (1 - rs) / (2 * (1 + rs))) / rs -
143
0
                             (1 + c_ * xs * (1 + d_ * xs))));
144
0
                } else {
145
0
                    return 0.0;
146
0
                }
147
0
            }
148
          private:
149
            Real a_, c_, d_, bs_, hk_;
150
        };
151
152
    }
153
154
    BivariateCumulativeNormalDistributionWe04DP::
155
    BivariateCumulativeNormalDistributionWe04DP(Real rho)
156
0
    : correlation_(rho) {
157
158
0
        QL_REQUIRE(rho>=-1.0,
159
0
                   "rho must be >= -1.0 (" << rho << " not allowed)");
160
0
        QL_REQUIRE(rho<=1.0,
161
0
                   "rho must be <= 1.0 (" << rho << " not allowed)");
162
0
    }
163
164
165
    Real BivariateCumulativeNormalDistributionWe04DP::operator()(
166
0
                                                       Real x, Real y) const {
167
168
        /* The implementation is described at section 2.4 "Hybrid
169
           Numerical Integration Algorithms" of "Numerical Computation
170
           of Rectangular Bivariate an Trivariate Normal and t
171
           Probabilities", Genz (2004), Statistics and Computing 14,
172
           151-160. (available at
173
           www.sci.wsu.edu/math/faculty/henz/homepage)
174
175
           The Gauss-Legendre quadrature have been extracted to
176
           TabulatedGaussLegendre (x,w zero-based)
177
178
           Tthe functions ot be integrated numerically have been moved
179
           to classes eqn3 and eqn6
180
181
           Change some magic numbers to M_PI */
182
183
0
        TabulatedGaussLegendre gaussLegendreQuad(20);
184
0
        if (std::fabs(correlation_) < 0.3) {
185
0
            gaussLegendreQuad.order(6);
186
0
        } else if (std::fabs(correlation_) < 0.75) {
187
0
            gaussLegendreQuad.order(12);
188
0
        }
189
190
0
        Real h = -x;
191
0
        Real k = -y;
192
0
        Real hk = h * k;
193
0
        Real BVN = 0.0;
194
195
0
        if (std::fabs(correlation_) < 0.925)
196
0
        {
197
0
            if (std::fabs(correlation_) > 0)
198
0
            {
199
0
                Real asr = std::asin(correlation_);
200
0
                eqn3 f(h,k,asr);
201
0
                BVN = gaussLegendreQuad(f);
202
0
                BVN *= asr * (0.25 / M_PI);
203
0
            }
204
0
            BVN += cumnorm_(-h) * cumnorm_(-k);
205
0
        }
206
0
        else
207
0
        {
208
0
            if (correlation_ < 0)
209
0
            {
210
0
                k *= -1;
211
0
                hk *= -1;
212
0
            }
213
0
            if (std::fabs(correlation_) < 1)
214
0
            {
215
0
                Real Ass = (1 - correlation_) * (1 + correlation_);
216
0
                Real a = std::sqrt(Ass);
217
0
                Real bs = (h-k)*(h-k);
218
0
                Real c = (4 - hk) / 8;
219
0
                Real d = (12 - hk) / 16;
220
0
                Real asr = -(bs / Ass + hk) / 2;
221
0
                if (asr > -100)
222
0
                {
223
0
                    BVN = a * std::exp(asr) *
224
0
                        (1 - c * (bs - Ass) * (1 - d * bs / 5) / 3 +
225
0
                         c * d * Ass * Ass / 5);
226
0
                }
227
0
                if (-hk < 100)
228
0
                {
229
0
                    Real B = std::sqrt(bs);
230
0
                    BVN -= std::exp(-hk / 2) * 2.506628274631 *
231
0
                        cumnorm_(-B / a) * B *
232
0
                        (1 - c * bs * (1 - d * bs / 5) / 3);
233
0
                }
234
0
                a /= 2;
235
0
                eqn6 f(a,c,d,bs,hk);
236
0
                BVN += gaussLegendreQuad(f);
237
0
                BVN /= (-2.0 * M_PI);
238
0
            }
239
240
0
            if (correlation_ > 0) {
241
0
                BVN += cumnorm_(-std::max(h, k));
242
0
            } else {
243
0
                BVN *= -1;
244
0
                if (k > h) {
245
                    // evaluate cumnorm where it is most precise, that
246
                    // is in the lower tail because of double accuracy
247
                    // around 0.0 vs around 1.0
248
0
                    if (h >= 0) {
249
0
                        BVN += cumnorm_(-h) - cumnorm_(-k);
250
0
                    } else {
251
0
                        BVN += cumnorm_(k) - cumnorm_(h);
252
0
                    }
253
0
                }
254
0
            }
255
0
        }
256
0
        return BVN;
257
0
    }
258
259
}