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