1 // -*- C++ -*-
2 /**
3 * @brief Efficient implementation of the CDF computation for a bi-dimensional
4 * Normal distribution
5 *
6 * Copyright 2005-2021 Airbus-EDF-IMACS-ONERA-Phimeca
7 *
8 * This library is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this library. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22 #include <cmath>
23
24 #include "openturns/Normal3DCDF.hxx"
25 #include "openturns/DistFunc.hxx"
26 #include "openturns/Point.hxx"
27
28 BEGIN_NAMESPACE_OPENTURNS
29
30 Scalar adonet(const Scalar h1,
31 const Scalar h2,
32 const Scalar h3,
33 const Scalar r23,
34 const Scalar a12,
35 const Scalar a13);
36
37 Scalar tvnf(const Scalar x,
38 const Scalar h1,
39 const Scalar h2,
40 const Scalar h3,
41 const Scalar r23,
42 const Scalar a12,
43 const Scalar a13);
44
45 void sincs(const Scalar x,
46 Scalar & sx,
47 Scalar & cs);
48
49 Scalar pntgnd(const Scalar ba,
50 const Scalar bb,
51 const Scalar bc,
52 const Scalar ra,
53 const Scalar rb,
54 const Scalar r,
55 const Scalar rr);
56
57 void krnrdt(const Scalar a,
58 const Scalar b,
59 const Scalar h1,
60 const Scalar h2,
61 const Scalar h3,
62 const Scalar r23,
63 const Scalar a12,
64 const Scalar a13,
65 Scalar & resk,
66 Scalar & err);
67
68 /*
69 Translated in C++ from Alan Genz's matlab routine tvnl.m
70 The routine tvnl.m has the following copyright:
71 % Copyright (C) 2011, Alan Genz, All rights reserved.
72 %
73 % Redistribution and use in source and binary forms, with or without
74 % modification, are permitted provided the following conditions are met:
75 % 1. Redistributions of source code must retain the above copyright
76 % notice, this list of conditions and the following disclaimer.
77 % 2. Redistributions in binary form must reproduce the above copyright
78 % notice, this list of conditions and the following disclaimer in the
79 % documentation and/or other materials provided with the distribution.
80 % 3. The contributor name(s) may not be used to endorse or promote
81 % products derived from this software without specific prior written
82 % permission.
83 % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
84 % "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
85 % LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
86 % FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
87 % COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
88 % INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
89 % BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
90 % OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
91 % ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
92 % TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
93 % USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
94 %
95 */
96 // Opposite convention for INF wrt Normal2DCDF
97 /*
98 #define NORMAL3DCDF_MINUS_INF -37.5193794
99 #define NORMAL3DCDF_PLUS_INF 8.29236108
100 #define NORMAL3DCDF_MIN_LOG -745.13321
101 #define NORMAL3DCDF_EPS 1.0e-14
102 #define NORMAL3DCDF_MAXINT 100
103 */
104 #define NORMAL3DCDF_MINUS_INF -37.5193794
105 #define NORMAL3DCDF_PLUS_INF 8.29236108
106 #define NORMAL3DCDF_MIN_LOG -745.13321
107 #define NORMAL3DCDF_EPS 1.0e-14
108 #define NORMAL3DCDF_MAXINT 100
109
Normal3DCDF(const Scalar x1,const Scalar x2,const Scalar x3,const Scalar rho12,const Scalar rho13,const Scalar rho23,const Bool tail)110 Scalar Normal3DCDF(const Scalar x1,
111 const Scalar x2,
112 const Scalar x3,
113 const Scalar rho12,
114 const Scalar rho13,
115 const Scalar rho23,
116 const Bool tail)
117 {
118 if (!(std::abs(rho12) <= 1.0)) throw InvalidArgumentException(HERE) << "Error: the correlation coefficient rho12 must be in [-1, 1], here rho12=" << rho12;
119 if (!(std::abs(rho13) <= 1.0)) throw InvalidArgumentException(HERE) << "Error: the correlation coefficient rho13 must be in [-1, 1], here rho12=" << rho13;
120 if (!(std::abs(rho23) <= 1.0)) throw InvalidArgumentException(HERE) << "Error: the correlation coefficient rho23 must be in [-1, 1], here rho23=" << rho23;
121 const Scalar delta = rho12 * rho12 + rho13 * rho13 + rho23 * rho23 - 2.0 * rho12 * rho13 * rho23;
122 if (!(delta <= 1.0)) throw InvalidArgumentException(HERE) << "Error: delta=rho12^2+rho13^2+rho23^2-2*rho12*rho13*rho23 must be in less or equal to 1, here delta=" << delta;
123 if (tail) return Normal3DCDF(-x1, -x2, -x3, rho12, rho13, rho23, false);
124 if ((x1 <= NORMAL3DCDF_MINUS_INF) || (x2 <= NORMAL3DCDF_MINUS_INF) || (x3 <= NORMAL3DCDF_MINUS_INF)) return 0.0;
125 if (x1 >= NORMAL3DCDF_PLUS_INF)
126 {
127 if (x2 >= NORMAL3DCDF_PLUS_INF)
128 {
129 if (x3 >= NORMAL3DCDF_PLUS_INF) return 1.0;
130 else return DistFunc::pNormal(x3);
131 } // x2 >= NORMAL3DCDF_PLUS_INF
132 // Here, x2 < NORMAL3DCDF_PLUS_INF
133 if (x3 >= NORMAL3DCDF_PLUS_INF) return DistFunc::pNormal(x2);
134 else return DistFunc::pNormal2D(x2, x3, rho23);
135 } // x1 >= NORMAL3DCDF_PLUS_INF
136 // Here, x1 < NORMAL3DCDF_PLUS_INF
137 if (x2 >= NORMAL3DCDF_PLUS_INF)
138 {
139 if (x3 >= NORMAL3DCDF_PLUS_INF) return DistFunc::pNormal(x1);
140 else return DistFunc::pNormal2D(x1, x3, rho13);
141 } // x2 >= NORMAL3DCDF_PLUS_INF
142 // Here, x1 < NORMAL3DCDF_PLUS_INF and x2 < NORMAL3DCDF_PLUS_INF
143 if (x3 >= NORMAL3DCDF_PLUS_INF) return DistFunc::pNormal2D(x1, x2, rho12);
144 // Here, we have to do some work!
145 // Probability of the negative orthant
146 if (std::abs(x1) + std::abs(x2) + std::abs(x3) < NORMAL3DCDF_EPS) return std::max(0.0, std::min(1.0, 0.125 * (1.0 + 2.0 * (std::asin(rho12) + std::asin(rho13) + std::asin(rho23)) / M_PI)));
147 Scalar h1 = x1;
148 Scalar h2 = x2;
149 Scalar h3 = x3;
150 Scalar r12 = rho12;
151 Scalar r13 = rho13;
152 Scalar r23 = rho23;
153 // Sort R's and check for special correlation structure
154 if (std::abs(r12) > std::abs(r13))
155 {
156 h2 = h3;
157 h3 = x2;
158 r12 = r13;
159 r13 = rho12;
160 }
161 if (std::abs(r13) > std::abs(r23))
162 {
163 h1 = h2;
164 h2 = x1;
165 r23 = r13;
166 r13 = rho23;
167 }
168 if (std::abs(r12) + std::abs(r13) < NORMAL3DCDF_EPS) return DistFunc::pNormal(h1) * DistFunc::pNormal2D(h2, h3, r23);
169 if (std::abs(r13) + std::abs(r23) < NORMAL3DCDF_EPS) return DistFunc::pNormal(h3) * DistFunc::pNormal2D(h1, h2, r12);
170 if (std::abs(r12) + std::abs(r23) < NORMAL3DCDF_EPS) return DistFunc::pNormal(h2) * DistFunc::pNormal2D(h1, h3, r13);
171 if (1.0 - r23 < NORMAL3DCDF_EPS) return DistFunc::pNormal2D(h1, std::min(h2, h3), r12);
172 if ((r23 + 1.0 < NORMAL3DCDF_EPS) && (h2 > -h3)) return std::max(0.0, std::min(1.0, DistFunc::pNormal2D(h1, h2, r12) - DistFunc::pNormal2D(h1, -h3, r12)));
173 // At last, the general case
174 const Scalar a12 = std::asin(r12);
175 const Scalar a13 = std::asin(r13);
176 return std::max(0.0, std::min(1.0, adonet(h1, h2, h3, r23, a12, a13) / (2.0 * M_PI) + DistFunc::pNormal(h1) * DistFunc::pNormal2D(h2, h3, r23)));
177 }
178
179 /*
180 Computes Plackett formula integrands
181 */
tvnf(const Scalar x,const Scalar h1,const Scalar h2,const Scalar h3,const Scalar r23,const Scalar a12,const Scalar a13)182 Scalar tvnf(const Scalar x,
183 const Scalar h1,
184 const Scalar h2,
185 const Scalar h3,
186 const Scalar r23,
187 const Scalar a12,
188 const Scalar a13)
189 {
190 Scalar result = 0.0;
191 Scalar r12 = -1.0;
192 Scalar rr2 = -1.0;
193 sincs(a12 * x, r12, rr2);
194 Scalar r13 = -1.0;
195 Scalar rr3 = -1.0;
196 sincs(a13 * x, r13, rr3);
197 if (std::abs(a12) > 0.0) result = a12 * pntgnd(h1, h2, h3, r13, r23, r12, rr2);
198 if (std::abs(a13) > 0.0) result += a13 * pntgnd(h1, h3, h2, r12, r23, r13, rr3);
199 return result;
200 }
201
202 /*
203 Computes accurately sin(x), cos(x)^2 for |x| near pi/2
204 */
sincs(const Scalar x,Scalar & sx,Scalar & cs)205 void sincs(const Scalar x,
206 Scalar & sx,
207 Scalar & cs)
208 {
209 const Scalar e = 0.5 * M_PI - std::abs(x);
210 const Scalar ee = e * e;
211 if (ee < 5.0e-5)
212 {
213 cs = ee * (1.0 - ee * (1.0 - 2.0 * ee / 15.0) / 3.0);
214 sx = (1.0 - 0.5 * ee * (1.0 - ee / 12.0));
215 if (x < 0.0) sx = -sx;
216 return;
217 }
218 sx = std::sin(x);
219 cs = 1.0 - sx * sx;
220 }
221
222 /*
223 Computes Plackett formula integrand
224 */
pntgnd(const Scalar ba,const Scalar bb,const Scalar bc,const Scalar ra,const Scalar rb,const Scalar r,const Scalar rr)225 Scalar pntgnd(const Scalar ba,
226 const Scalar bb,
227 const Scalar bc,
228 const Scalar ra,
229 const Scalar rb,
230 const Scalar r,
231 const Scalar rr)
232 {
233 Scalar result = 0.0;
234 const Scalar dt = rr * (rr - (ra - rb) * (ra - rb) - 2.0 * ra * rb * (1.0 - r));
235 if (dt > 0.0)
236 {
237 const Scalar bt = (bc * rr + ba * (r * rb - ra) + bb * (r * ra - rb)) / std::sqrt(dt);
238 const Scalar delta = ba - r * bb;
239 const Scalar ft = delta * delta / rr + bb * bb;
240 if ((ft < -2.0 * NORMAL3DCDF_MIN_LOG) && (bt > NORMAL3DCDF_MINUS_INF))
241 {
242 result = std::exp(-0.5 * ft);
243 if (bt < NORMAL3DCDF_PLUS_INF) result *= DistFunc::pNormal(bt);
244 }
245 }
246 return result;
247 }
248
249 /*
250 1D adaptive integration
251 */
adonet(const Scalar h1,const Scalar h2,const Scalar h3,const Scalar r23,const Scalar a12,const Scalar a13)252 Scalar adonet(const Scalar h1,
253 const Scalar h2,
254 const Scalar h3,
255 const Scalar r23,
256 const Scalar a12,
257 const Scalar a13)
258 {
259 Scalar result = 0.0;
260 Scalar ai[NORMAL3DCDF_MAXINT];
261 ai[0] = 0.0;
262 Scalar bi[NORMAL3DCDF_MAXINT];
263 bi[0] = 1.0;
264 Scalar fi[NORMAL3DCDF_MAXINT];
265 Scalar ei[NORMAL3DCDF_MAXINT];
266 UnsignedInteger ip = 0;
267 UnsignedInteger im = 0;
268 Scalar err = 1.0;
269 while ((err > 0.25 * NORMAL3DCDF_EPS) && (im < NORMAL3DCDF_MAXINT - 1))
270 {
271 ++im;
272 bi[im] = bi[ip];
273 ai[im] = 0.5 * (ai[ip] + bi[ip]);
274 bi[ip] = ai[im];
275 krnrdt(ai[ip], bi[ip], h1, h2, h3, r23, a12, a13, fi[ip], ei[ip]);
276 krnrdt(ai[im], bi[im], h1, h2, h3, r23, a12, a13, fi[im], ei[im]);
277 UnsignedInteger iErrMax = 0;
278 Scalar errMax = 0.0;
279 err = 0.0;
280 result = 0.0;
281 for (UnsignedInteger i = 0; i <= im; ++i)
282 {
283 const Scalar localError = ei[i];
284 result += fi[i];
285 err += localError * localError;
286 if (localError > errMax)
287 {
288 errMax = localError;
289 iErrMax = i;
290 }
291 }
292 ip = iErrMax;
293 err = std::sqrt(err);
294 } // while (err >...)
295 return result;
296 }
297
298 /*
299 Kronrod rule
300 */
krnrdt(const Scalar a,const Scalar b,const Scalar h1,const Scalar h2,const Scalar h3,const Scalar r23,const Scalar a12,const Scalar a13,Scalar & resk,Scalar & err)301 void krnrdt(const Scalar a,
302 const Scalar b,
303 const Scalar h1,
304 const Scalar h2,
305 const Scalar h3,
306 const Scalar r23,
307 const Scalar a12,
308 const Scalar a13,
309 Scalar & resk,
310 Scalar & err)
311 {
312 static Scalar wg0(0.2729250867779007);
313 static Scalar wg[5] = {0.05566856711617449, 0.1255803694649048, 0.1862902109277352, 0.2331937645919914, 0.2628045445102478};
314 static Scalar xgk[11] = {0.9963696138895427, 0.9782286581460570, 0.9416771085780681, 0.8870625997680953, 0.8160574566562211, 0.7301520055740492, 0.6305995201619651, 0.5190961292068118, 0.3979441409523776, 0.2695431559523450, 0.1361130007993617};
315 static Scalar wgk0(0.1365777947111183);
316 static Scalar wgk[11] = {0.00976544104596129, 0.02715655468210443, 0.04582937856442671, 0.06309742475037484, 0.07866457193222764, 0.09295309859690074, 0.1058720744813894, 0.1167395024610472, 0.1251587991003195, 0.1312806842298057, 0.1351935727998845};
317 const Scalar wid = 0.5 * (b - a);
318 const Scalar cen = 0.5 * (a + b);
319 Scalar fc = tvnf(cen, h1, h2, h3, r23, a12, a13);
320 Scalar resg = fc * wg0;
321 resk = fc * wgk0;
322 Scalar t = 0.0;
323 for (UnsignedInteger j = 0; j < 5; ++j)
324 {
325 t = wid * xgk[2 * j];
326 fc = tvnf(cen - t, h1, h2, h3, r23, a12, a13) + tvnf(cen + t, h1, h2, h3, r23, a12, a13);
327 resk += wgk[2 * j] * fc;
328 t = wid * xgk[2 * j + 1];
329 fc = tvnf(cen - t, h1, h2, h3, r23, a12, a13) + tvnf(cen + t, h1, h2, h3, r23, a12, a13);
330 resk += wgk[2 * j + 1] * fc;
331 resg += wg[j] * fc;
332 }
333 t = wid * xgk[10];
334 fc = tvnf(cen - t, h1, h2, h3, r23, a12, a13) + tvnf(cen + t, h1, h2, h3, r23, a12, a13);
335 resk = wid * (resk + wgk[10] * fc);
336 err = std::abs(resk - wid * resg);
337 }
338
339 #undef NORMAL3DCDF_INF
340 #undef NORMAL3DCDF_LOG
341 #undef NORMAL3DCDF_EPS
342 #undef NORMAL3DCDF_MAXINT
343
344 END_NAMESPACE_OPENTURNS
345