/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 | | } |