/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 | | #include <algorithm> |
26 | | #include <cmath> |
27 | | |
28 | | namespace QuantLib { |
29 | | |
30 | | // Drezner 1978 |
31 | | |
32 | | const Real BivariateCumulativeNormalDistributionDr78::x_[] = { |
33 | | 0.24840615, |
34 | | 0.39233107, |
35 | | 0.21141819, |
36 | | 0.03324666, |
37 | | 0.00082485334 |
38 | | }; |
39 | | |
40 | | const Real BivariateCumulativeNormalDistributionDr78::y_[] = { |
41 | | 0.10024215, |
42 | | 0.48281397, |
43 | | 1.06094980, |
44 | | 1.77972940, |
45 | | 2.66976040000 |
46 | | }; |
47 | | |
48 | | BivariateCumulativeNormalDistributionDr78:: |
49 | | BivariateCumulativeNormalDistributionDr78(Real rho) |
50 | 0 | : rho_(rho), rho2_(rho*rho) { |
51 | |
|
52 | 0 | QL_REQUIRE(rho>=-1.0, |
53 | 0 | "rho must be >= -1.0 (" << rho << " not allowed)"); |
54 | 0 | QL_REQUIRE(rho<=1.0, |
55 | 0 | "rho must be <= 1.0 (" << rho << " not allowed)"); |
56 | 0 | } |
57 | | |
58 | | Real BivariateCumulativeNormalDistributionDr78::operator()(Real a, |
59 | 0 | Real b) const { |
60 | |
|
61 | 0 | CumulativeNormalDistribution cumNormalDist; |
62 | 0 | Real CumNormDistA = cumNormalDist(a); |
63 | 0 | Real CumNormDistB = cumNormalDist(b); |
64 | 0 | Real MaxCumNormDistAB = std::max(CumNormDistA, CumNormDistB); |
65 | 0 | Real MinCumNormDistAB = std::min(CumNormDistA, CumNormDistB); |
66 | |
|
67 | 0 | if (rho_ == 1.0) |
68 | 0 | return MinCumNormDistAB; |
69 | | |
70 | 0 | if (rho_ == -1.0) |
71 | 0 | return std::max<Real>(0.0, CumNormDistA + CumNormDistB - 1.0); |
72 | | |
73 | 0 | if (1.0-MaxCumNormDistAB<1e-15) |
74 | 0 | return MinCumNormDistAB; |
75 | | |
76 | 0 | if (MinCumNormDistAB<1e-15) |
77 | 0 | return MinCumNormDistAB; |
78 | | |
79 | 0 | Real a1 = a / std::sqrt(2.0 * (1.0 - rho2_)); |
80 | 0 | Real b1 = b / std::sqrt(2.0 * (1.0 - rho2_)); |
81 | |
|
82 | 0 | Real result=-1.0; |
83 | |
|
84 | 0 | if (a<=0.0 && b<=0 && rho_<=0) { |
85 | 0 | Real sum=0.0; |
86 | 0 | for (Size i=0; i<5; i++) { |
87 | 0 | for (Size j=0;j<5; j++) { |
88 | 0 | sum += x_[i]*x_[j]* |
89 | 0 | std::exp(a1*(2.0*y_[i]-a1)+b1*(2.0*y_[j]-b1) |
90 | 0 | +2.0*rho_*(y_[i]-a1)*(y_[j]-b1)); |
91 | 0 | } |
92 | 0 | } |
93 | 0 | result = std::sqrt(1.0 - rho2_)/M_PI*sum; |
94 | 0 | } else if (a<=0 && b>=0 && rho_>=0) { |
95 | 0 | BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(-rho_); |
96 | 0 | result= CumNormDistA - bivCumNormalDist(a, -b); |
97 | 0 | } else if (a>=0.0 && b<=0.0 && rho_>=0.0) { |
98 | 0 | BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(-rho_); |
99 | 0 | result= CumNormDistB - bivCumNormalDist(-a, b); |
100 | 0 | } else if (a>=0.0 && b>=0.0 && rho_<=0.0) { |
101 | 0 | result= CumNormDistA + CumNormDistB -1.0 + (*this)(-a, -b); |
102 | 0 | } else if (a*b*rho_>0.0) { |
103 | | // D^2 = (rho*a - b)^2 + a^2*(1 - rho^2), and likewise for b. |
104 | | // The hypot forms avoid cancellation near |rho| = 1. |
105 | 0 | const Real sqrtOneMinusRho2 = std::sqrt((1.0 - rho_) * (1.0 + rho_)); |
106 | 0 | const Real maxCorrelation = std::nextafter(Real(1.0), Real(2.0)); |
107 | 0 | Real rho1 = (rho_*a-b)*(a>0.0 ? 1.0: -1.0)/ |
108 | 0 | std::hypot(rho_*a-b, a*sqrtOneMinusRho2); |
109 | | // Clamp only finite one-ulp overshoots of the correlation range. |
110 | 0 | if (std::fabs(rho1) == maxCorrelation) |
111 | 0 | rho1 = std::clamp(rho1, Real(-1.0), Real(1.0)); |
112 | 0 | BivariateCumulativeNormalDistributionDr78 bivCumNormalDist(rho1); |
113 | |
|
114 | 0 | Real rho2 = (rho_*b-a)*(b>0.0 ? 1.0: -1.0)/ |
115 | 0 | std::hypot(rho_*b-a, b*sqrtOneMinusRho2); |
116 | 0 | if (std::fabs(rho2) == maxCorrelation) |
117 | 0 | rho2 = std::clamp(rho2, Real(-1.0), Real(1.0)); |
118 | 0 | BivariateCumulativeNormalDistributionDr78 CBND2(rho2); |
119 | |
|
120 | 0 | Real delta = (1.0-(a>0.0 ? 1.0: -1.0)*(b>0.0 ? 1.0: -1.0))/4.0; |
121 | |
|
122 | 0 | result= bivCumNormalDist(a, 0.0) + CBND2(b, 0.0) - delta; |
123 | 0 | } else { |
124 | 0 | QL_FAIL("case not handled"); |
125 | 0 | } |
126 | | |
127 | 0 | return result; |
128 | 0 | } |
129 | | |
130 | | |
131 | | // West 2004 |
132 | | |
133 | | namespace { |
134 | | |
135 | | class eqn3 { /* Relates to eqn3 Genz 2004 */ |
136 | | public: |
137 | | eqn3(Real h, Real k, Real asr) |
138 | 430 | : hk_(h * k), asr_(asr), hs_((h * h + k * k) / 2) {} |
139 | | |
140 | 5.24k | Real operator()(Real x) const { |
141 | 5.24k | Real sn = std::sin(asr_ * (-x + 1) * 0.5); |
142 | 5.24k | return std::exp((sn * hk_ - hs_) / (1.0 - sn * sn)); |
143 | 5.24k | } |
144 | | private: |
145 | | Real hk_, asr_, hs_; |
146 | | }; |
147 | | |
148 | | class eqn6 { /* Relates to eqn6 Genz 2004 */ |
149 | | public: |
150 | | eqn6(Real a, Real c, Real d, Real bs, Real hk) |
151 | 870 | : a_(a), c_(c), d_(d), bs_(bs), hk_(hk) {} |
152 | 17.4k | Real operator()(Real x) const { |
153 | 17.4k | Real xs = a_ * (-x + 1); |
154 | 17.4k | xs = std::fabs(xs*xs); |
155 | 17.4k | Real rs = std::sqrt(1 - xs); |
156 | 17.4k | Real asr = -(bs_ / xs + hk_) / 2; |
157 | 17.4k | if (asr > -100.0) { |
158 | 6.38k | return (a_ * std::exp(asr) * |
159 | 6.38k | (std::exp(-hk_ * (1 - rs) / (2 * (1 + rs))) / rs - |
160 | 6.38k | (1 + c_ * xs * (1 + d_ * xs)))); |
161 | 11.0k | } else { |
162 | 11.0k | return 0.0; |
163 | 11.0k | } |
164 | 17.4k | } |
165 | | private: |
166 | | Real a_, c_, d_, bs_, hk_; |
167 | | }; |
168 | | |
169 | | } |
170 | | |
171 | | BivariateCumulativeNormalDistributionWe04DP:: |
172 | | BivariateCumulativeNormalDistributionWe04DP(Real rho) |
173 | 1.30k | : correlation_(rho) { |
174 | | |
175 | 1.30k | QL_REQUIRE(rho>=-1.0, |
176 | 1.30k | "rho must be >= -1.0 (" << rho << " not allowed)"); |
177 | 1.30k | QL_REQUIRE(rho<=1.0, |
178 | 1.30k | "rho must be <= 1.0 (" << rho << " not allowed)"); |
179 | 1.30k | } |
180 | | |
181 | | |
182 | | Real BivariateCumulativeNormalDistributionWe04DP::operator()( |
183 | 1.30k | Real x, Real y) const { |
184 | | |
185 | | /* The implementation is described at section 2.4 "Hybrid |
186 | | Numerical Integration Algorithms" of "Numerical Computation |
187 | | of Rectangular Bivariate an Trivariate Normal and t |
188 | | Probabilities", Genz (2004), Statistics and Computing 14, |
189 | | 151-160. (available at |
190 | | www.sci.wsu.edu/math/faculty/henz/homepage) |
191 | | |
192 | | The Gauss-Legendre quadrature have been extracted to |
193 | | TabulatedGaussLegendre (x,w zero-based) |
194 | | |
195 | | Tthe functions ot be integrated numerically have been moved |
196 | | to classes eqn3 and eqn6 |
197 | | |
198 | | Change some magic numbers to M_PI */ |
199 | | |
200 | 1.30k | TabulatedGaussLegendre gaussLegendreQuad(20); |
201 | 1.30k | if (std::fabs(correlation_) < 0.3) { |
202 | 129 | gaussLegendreQuad.order(6); |
203 | 1.17k | } else if (std::fabs(correlation_) < 0.75) { |
204 | 197 | gaussLegendreQuad.order(12); |
205 | 197 | } |
206 | | |
207 | 1.30k | Real h = -x; |
208 | 1.30k | Real k = -y; |
209 | 1.30k | Real hk = h * k; |
210 | 1.30k | Real BVN = 0.0; |
211 | | |
212 | 1.30k | if (std::fabs(correlation_) < 0.925) |
213 | 432 | { |
214 | 432 | if (std::fabs(correlation_) > 0) |
215 | 430 | { |
216 | 430 | Real asr = std::asin(correlation_); |
217 | 430 | eqn3 f(h,k,asr); |
218 | 430 | BVN = gaussLegendreQuad(f); |
219 | 430 | BVN *= asr * (0.25 / M_PI); |
220 | 430 | } |
221 | 432 | BVN += cumnorm_(-h) * cumnorm_(-k); |
222 | 432 | } |
223 | 870 | else |
224 | 870 | { |
225 | 870 | if (correlation_ < 0) |
226 | 797 | { |
227 | 797 | k *= -1; |
228 | 797 | hk *= -1; |
229 | 797 | } |
230 | 870 | if (std::fabs(correlation_) < 1) |
231 | 870 | { |
232 | 870 | Real Ass = (1 - correlation_) * (1 + correlation_); |
233 | 870 | Real a = std::sqrt(Ass); |
234 | 870 | Real bs = (h-k)*(h-k); |
235 | 870 | Real c = (4 - hk) / 8; |
236 | 870 | Real d = (12 - hk) / 16; |
237 | 870 | Real asr = -(bs / Ass + hk) / 2; |
238 | 870 | if (asr > -100) |
239 | 507 | { |
240 | 507 | BVN = a * std::exp(asr) * |
241 | 507 | (1 - c * (bs - Ass) * (1 - d * bs / 5) / 3 + |
242 | 507 | c * d * Ass * Ass / 5); |
243 | 507 | } |
244 | 870 | if (-hk < 100) |
245 | 805 | { |
246 | 805 | Real B = std::sqrt(bs); |
247 | 805 | BVN -= std::exp(-hk / 2) * 2.506628274631 * |
248 | 805 | cumnorm_(-B / a) * B * |
249 | 805 | (1 - c * bs * (1 - d * bs / 5) / 3); |
250 | 805 | } |
251 | 870 | a /= 2; |
252 | 870 | eqn6 f(a,c,d,bs,hk); |
253 | 870 | BVN += gaussLegendreQuad(f); |
254 | 870 | BVN /= (-2.0 * M_PI); |
255 | 870 | } |
256 | | |
257 | 870 | if (correlation_ > 0) { |
258 | 73 | BVN += cumnorm_(-std::max(h, k)); |
259 | 797 | } else { |
260 | 797 | BVN *= -1; |
261 | 797 | if (k > h) { |
262 | | // evaluate cumnorm where it is most precise, that |
263 | | // is in the lower tail because of double accuracy |
264 | | // around 0.0 vs around 1.0 |
265 | 274 | if (h >= 0) { |
266 | 77 | BVN += cumnorm_(-h) - cumnorm_(-k); |
267 | 197 | } else { |
268 | 197 | BVN += cumnorm_(k) - cumnorm_(h); |
269 | 197 | } |
270 | 274 | } |
271 | 797 | } |
272 | 870 | } |
273 | 1.30k | return BVN; |
274 | 1.30k | } |
275 | | |
276 | | } |