single-gamic.c
1 //! @file single-gamic.c
2 //! @author J. Marcel van der Veer
3
4 //! @section Copyright
5 //!
6 //! This file is part of Algol68G - an Algol 68 compiler-interpreter.
7 //! Copyright 2001-2024 J. Marcel van der Veer [algol68g@xs4all.nl].
8
9 //! @section License
10 //!
11 //! This program is free software; you can redistribute it and/or modify it
12 //! under the terms of the GNU General Public License as published by the
13 //! Free Software Foundation; either version 3 of the License, or
14 //! (at your option) any later version.
15 //!
16 //! This program is distributed in the hope that it will be useful, but
17 //! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 //! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
19 //! more details. You should have received a copy of the GNU General Public
20 //! License along with this program. If not, see [http://www.gnu.org/licenses/].
21
22 //! @section Synopsis
23 //!
24 //! REAL generalised incomplete gamma function.
25
26 // Generalised incomplete gamma code in this file was downloaded from
27 //
28 // [http://helios.mi.parisdescartes.fr/~rabergel/]
29 //
30 // and adapted for Algol 68 Genie.
31 //
32 // Reference:
33 // Rémy Abergel, Lionel Moisan. Fast and accurate evaluation of a
34 // generalized incomplete gamma function. 2019. hal-01329669v2
35 //
36 // Original source code copyright and license:
37 //
38 // DELTAGAMMAINC Fast and Accurate Evaluation of a Generalized Incomplete Gamma
39 // Function. Copyright (C) 2016 Remy Abergel (remy.abergel AT gmail.com), Lionel
40 // Moisan (Lionel.Moisan AT parisdescartes.fr).
41 //
42 // This file is a part of the DELTAGAMMAINC software, dedicated to the
43 // computation of a generalized incomplete gammafunction. See the Companion paper
44 // for a complete description of the algorithm.
45 // ``Fast and accurate evaluation of a generalized incomplete gamma function''
46 // (Rémy Abergel, Lionel Moisan), preprint MAP5 nº2016-14, revision 1.
47 //
48 // This program is free software: you can redistribute it and/or modify it under
49 // the terms of the GNU General Public License as published by the Free Software
50 // Foundation, either version 3 of the License, or (at your option) any later
51 // version.
52 // This program is distributed in the hope that it will be useful, but WITHOUT
53 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
54 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
55 // details.
56 //
57 // You should have received a copy of the GNU General Public License along with
58 // this program. If not, see [http://www.gnu.org/licenses/].
59
60 // References
61 // R. Abergel and L. Moisan. 2016. Fast and accurate evaluation of a
62 // generalized incomplete gamma function, preprint MAP5 nº2016-14, revision 1
63 // Rémy Abergel, Lionel Moisan. Fast and accurate evaluation of a
64 // generalized incomplete gamma function. 2019. hal-01329669v2
65 // F. W. J. Olver, D. W. Lozier, R. F. Boisvert, and C. W. Clark
66 // (Eds.). 2010. NIST Handbook of Mathematical Functions. Cambridge University
67 // Press. (see online version at [http://dlmf.nist.gov/])
68 // W. H. Press, S. A. Teukolsky, W. T. Vetterling, and
69 // B. P. Flannery. 1992. Numerical recipes in C: the art of scientific
70 // computing (2nd ed.).
71 // G. R. Pugh, 2004. An analysis of the Lanczos Gamma approximation (phd
72 // thesis)
73
74 #include "a68g.h"
75 #include "a68g-genie.h"
76 #include "a68g-prelude.h"
77 #include "a68g-lib.h"
78 #include "a68g-mp.h"
79
80 #define ITMAX 1000000000 // Maximum allowed number of iterations
81 #define NITERMAX_ROMBERG 15 // Maximum allowed number of Romberg iterations
82 #define TOL_ROMBERG 0.1 // Tolerance factor used to stop the Romberg iterations
83 #define TOL_DIFF 0.2 // Tolerance factor used for the approximation of I_{x,y}^{mu,p} using differences
84
85 //! @brief compute plim (x), the limit of the partition of the domain (p,x)
86 // detailed in the paper.
87 // | x if 0 < x
88 // |
89 // plim (x) = < 0 if -9 <= x <= 0
90 // |
91 // | 5.*sqrt (|x|)-5. otherwise
92
93 REAL_T plim (REAL_T x)
94 {
95 return (x >= 0) ? x : ((x >= -9) ? 0 : 5 * sqrt (-x) - 5);
96 }
97
98 //! @brief compute G(p,x) in the domain x <= p using a continued fraction
99 // p >= 0
100 // x <= p
101
102 void G_cfrac_lower (REAL_T * Gcfrac, REAL_T p, REAL_T x)
103 {
104 // deal with special case
105 if (x == 0) {
106 *Gcfrac = 0;
107 return;
108 }
109 // Evaluate the continued fraction using Modified Lentz's method. However,
110 // as detailed in the paper, perform manually the first pass (n=1), of the
111 // initial Modified Lentz's method.
112 REAL_T an = 1, bn = p, del;
113 REAL_T f = an / bn, c = an / A68_REAL_MIN, d = 1 / bn;
114 INT_T n = 2, k;
115 do {
116 k = n / 2;
117 an = (n & 1 ? k : -(p - 1 + k)) * x;
118 bn++;
119 d = an * d + bn;
120 if (d == 0) {
121 d = A68_REAL_MIN;
122 }
123 c = bn + an / c;
124 if (c == 0) {
125 c = A68_REAL_MIN;
126 }
127 d = 1 / d;
128 del = d * c;
129 f *= del;
130 n++;
131 }
132 while ((a68_abs_real (del - 1.0) >= A68_REAL_EPS) && (n < ITMAX));
133 *Gcfrac = f;
134 }
135
136 //! @brief compute the G-function in the domain x < 0 and |x| < max (1,p-1)
137 // using a recursive integration by parts relation.
138 // This function cannot be used when mu > 0.
139 // p > 0, integer
140 // x < 0, |x| < max (1,p-1)
141
142 void G_ibp (REAL_T * Gibp, REAL_T p, REAL_T x)
143 {
144 REAL_T t = a68_abs_real (x);
145 REAL_T tt = 1 / (t * t);
146 BOOL_T odd = (INT_T) a68_int_real (p) % 2 != 0;
147 REAL_T c = 1 / t;
148 REAL_T d = (p - 1);
149 REAL_T s = c * (t - d);
150 INT_T l = 0;
151 BOOL_T stop;
152 do {
153 c *= d * (d - 1) * tt;
154 d -= 2;
155 REAL_T del = c * (t - d);
156 s += del;
157 l++;
158 stop = a68_abs_real (del) < a68_abs_real (s) * A68_REAL_EPS;
159 }
160 while ((l < floor ((p - 2) / 2)) && !stop);
161 if (odd && !stop) {
162 s += d * c / t;
163 }
164 *Gibp = ((odd ? -1 : 1) * a68_exp_real (-t + lgamma (p) - (p - 1) * a68_ln_real (t)) + s) / t;
165 }
166
167 //! @brief compute the G-function in the domain x > p using a
168 // continued fraction.
169 // p > 0
170 // x > p, or x = +infinity
171
172 void G_cfrac_upper (REAL_T * Gcfrac, REAL_T p, REAL_T x)
173 {
174 // Special case
175 if (a68_isinf_real (x)) {
176 *Gcfrac = 0;
177 return;
178 }
179 // Evaluate the continued fraction using Modified Lentz's method. However,
180 // as detailed in the paper, perform manually the first pass (n=1), of the
181 // initial Modified Lentz's method.
182 REAL_T an = 1, bn = x + 1 - p, c, d, del, f;
183 BOOL_T t = (bn != 0);
184 INT_T n;
185 if (t) {
186 // b{1} is non-zero
187 f = an / bn;
188 c = an / A68_REAL_MIN;
189 d = 1 / bn;
190 n = 2;
191 } else {
192 // b{1}=0 but b{2} is non-zero, compute Mcfrac = a{1}/f with f = a{2}/(b{2}+) a{3}/(b{3}+) ...
193 an = -(1 - p);
194 bn = x + 3 - p;
195 f = an / bn;
196 c = an / A68_REAL_MIN;
197 d = 1 / bn;
198 n = 3;
199 }
200 INT_T i = n - 1;
201 do {
202 an = -i * (i - p);
203 bn += 2;
204 d = an * d + bn;
205 if (d == 0) {
206 d = A68_REAL_MIN;
207 }
208 c = bn + an / c;
209 if (c == 0) {
210 c = A68_REAL_MIN;
211 }
212 d = 1 / d;
213 del = d * c;
214 f *= del;
215 i++;
216 n++;
217 }
218 while ((a68_abs_real (del - 1.0) >= A68_REAL_EPS) && (n < ITMAX));
219 *Gcfrac = t ? f : 1 / f;
220 }
221
222 //! @brief compute G : (p,x) --> R defined as follows
223 // if x <= p:
224 // G(p,x) = exp (x-p*ln (|x|)) * integral of s^{p-1} * exp (-sign (x)*s) ds from s = 0 to |x|
225 // otherwise:
226 // G(p,x) = exp (x-p*ln (|x|)) * integral of s^{p-1} * exp (-s) ds from s = x to infinity
227 // p > 0
228 // x is a real number or +infinity.
229
230 void G_func (REAL_T * G, REAL_T p, REAL_T x)
231 {
232 if (p >= plim (x)) {
233 G_cfrac_lower (G, p, x);
234 } else if (x < 0) {
235 G_ibp (G, p, x);
236 } else {
237 G_cfrac_upper (G, p, x);
238 }
239 }
240
241 //! @brief iteration of the Romberg approximation of I_{x,y}^{mu,p}
242
243 void romberg_iterations (REAL_T * R, REAL_T sigma, INT_T n, REAL_T x, REAL_T y, REAL_T mu, REAL_T p, REAL_T h, REAL_T pow2)
244 {
245 INT_T adr0_prev = ((n - 1) * n) / 2;
246 INT_T adr0 = (n * (n + 1)) / 2;
247 REAL_T sum = 0, xx;
248 for (int j = 1; j <= pow2; j++) {
249 xx = x + ((y - x) * (2 * j - 1)) / (2 * pow2);
250 sum += a68_exp_real (-mu * xx + (p - 1) * a68_ln_real (xx) - sigma);
251 }
252 R[adr0] = 0.5 * R[adr0_prev] + h * sum;
253 REAL_T pow4 = 4;
254 for (int m = 1; m <= n; m++) {
255 R[adr0 + m] = (pow4 * R[adr0 + (m - 1)] - R[adr0_prev + (m - 1)]) / (pow4 - 1);
256 pow4 *= 4;
257 }
258 }
259
260 //! @ compute I_{x,y}^{mu,p} using a Romberg approximation.
261 // Compute rho and sigma so I_{x,y}^{mu,p} = rho * exp (sigma)
262
263 void romberg_estimate (REAL_T * rho, REAL_T * sigma, REAL_T x, REAL_T y, REAL_T mu, REAL_T p)
264 {
265 REAL_T *R = (REAL_T *) get_heap_space (((NITERMAX_ROMBERG + 1) * (NITERMAX_ROMBERG + 2)) / 2 * sizeof (REAL_T));
266 ASSERT (R != NULL);
267 // Initialization (n=1)
268 *sigma = -mu * y + (p - 1) * a68_ln_real (y);
269 R[0] = 0.5 * (y - x) * (a68_exp_real (-mu * x + (p - 1) * a68_ln_real (x) - (*sigma)) + 1);
270 // Loop for n > 0
271 REAL_T relneeded = A68_REAL_EPS / TOL_ROMBERG;
272 INT_T adr0 = 0, n = 1;
273 REAL_T h = (y - x) / 2; // n=1, h = (y-x)/2^n
274 REAL_T pow2 = 1; // n=1; pow2 = 2^(n-1)
275 if (NITERMAX_ROMBERG >= 1) {
276 REAL_T relerr;
277 do {
278 romberg_iterations (R, *sigma, n, x, y, mu, p, h, pow2);
279 h /= 2;
280 pow2 *= 2;
281 adr0 = (n * (n + 1)) / 2;
282 relerr = a68_abs_real ((R[adr0 + n] - R[adr0 + n - 1]) / R[adr0 + n]);
283 n++;
284 } while (n <= NITERMAX_ROMBERG && relerr > relneeded);
285 }
286 // save Romberg estimate and free memory
287 *rho = R[adr0 + (n - 1)];
288 a68_free (R);
289 }
290
291 //! @brief compute generalized incomplete gamma function I_{x,y}^{mu,p}
292 // I_{x,y}^{mu,p} = integral from x to y of s^{p-1} * exp (-mu*s) ds
293 // This procedure computes (rho, sigma) described below.
294 // The approximated value of I_{x,y}^{mu,p} is I = rho * exp (sigma)
295 // mu is a real number non equal to zero
296 // (in general we take mu = 1 or -1 but any nonzero real number is allowed)
297 // x, y are two numbers with 0 <= x <= y <= +infinity,
298 // (the setting y=+infinity is allowed only when mu > 0)
299 // p is a real number > 0, p must be an integer when mu < 0.
300
301 void deltagammainc (REAL_T * rho, REAL_T * sigma, REAL_T x, REAL_T y, REAL_T mu, REAL_T p)
302 {
303 // Particular cases
304 if (a68_isinf_real (x) && a68_isinf_real (y)) {
305 *rho = 0;
306 *sigma = a68_neginf_double_real ();
307 return;
308 } else if (x == y) {
309 *rho = 0;
310 *sigma = a68_neginf_double_real ();
311 return;
312 }
313 if (x == 0 && a68_isinf_real (y)) {
314 *rho = 1;
315 (*sigma) = lgamma (p) - p * a68_ln_real (mu);
316 return;
317 }
318 // Initialization
319 REAL_T mA, mB, mx, my, nA, nB;
320 G_func (&mx, p, mu * x);
321 REAL_T nx = (a68_isinf_real (x) ? a68_neginf_double_real () : -mu * x + p * a68_ln_real (x));
322 G_func (&my, p, mu * y);
323 REAL_T ny = (a68_isinf_real (y) ? a68_neginf_double_real () : -mu * y + p * a68_ln_real (y));
324
325 // Compute (mA,nA) and (mB,nB) such as I_{x,y}^{mu,p} can be
326 // approximated by the difference A-B, where A >= B >= 0, A = mA*exp (nA) an
327 // B = mB*exp (nB). When the difference involves more than one digit loss due to
328 // cancellation errors, the integral I_{x,y}^{mu,p} is evaluated using the
329 // Romberg approximation method.
330
331 if (mu < 0) {
332 mA = my;
333 nA = ny;
334 mB = mx;
335 nB = nx;
336 } else {
337 if (p < plim (mu * x)) {
338 mA = mx;
339 nA = nx;
340 mB = my;
341 nB = ny;
342 } else if (p < plim (mu * y)) {
343 mA = 1;
344 nA = lgamma (p) - p * a68_ln_real (mu);
345 nB = fmax (nx, ny);
346 mB = mx * a68_exp_real (nx - nB) + my * a68_exp_real (ny - nB);
347 } else {
348 mA = my;
349 nA = ny;
350 mB = mx;
351 nB = nx;
352 }
353 }
354 // Compute (rho,sigma) such that rho*exp (sigma) = A-B
355 *rho = mA - mB * a68_exp_real (nB - nA);
356 *sigma = nA;
357 // If the difference involved a significant loss of precision, compute Romberg estimate.
358 if (!a68_isinf_real (y) && ((*rho) / mA < TOL_DIFF)) {
359 romberg_estimate (rho, sigma, x, y, mu, p);
360 }
361 }
362
363 // A68G Driver routines
364
365 //! @brief PROC gamma inc g = (REAL p, x, y, mu) REAL
366
367 void genie_gamma_inc_g_real (NODE_T * n)
368 {
369 A68_REAL x, y, mu, p;
370 POP_OBJECT (n, &mu, A68_REAL);
371 POP_OBJECT (n, &y, A68_REAL);
372 POP_OBJECT (n, &x, A68_REAL);
373 POP_OBJECT (n, &p, A68_REAL);
374 REAL_T rho, sigma;
375 deltagammainc (&rho, &sigma, VALUE (&x), VALUE (&y), VALUE (&mu), VALUE (&p));
376 PUSH_VALUE (n, rho * a68_exp_real (sigma), A68_REAL);
377 }
378
379 //! @brief PROC gamma inc f = (REAL p, x) REAL
380
381 void genie_gamma_inc_f_real (NODE_T * n)
382 {
383 A68_REAL x, p;
384 POP_OBJECT (n, &x, A68_REAL);
385 POP_OBJECT (n, &p, A68_REAL);
386 REAL_T rho, sigma;
387 deltagammainc (&rho, &sigma, VALUE (&x), a68_posinf_real (), 1, VALUE (&p));
388 PUSH_VALUE (n, rho * a68_exp_real (sigma), A68_REAL);
389 }
390
391 //! @brief PROC gamma inc = (REAL p, x) REAL
392
393 void genie_gamma_inc_h_real (NODE_T * n)
394 {
395 #if (A68_LEVEL >= 3) && defined (HAVE_GNU_MPFR)
396 genie_gamma_inc_real_mpfr (n);
397 #else
398 genie_gamma_inc_f_real (n);
399 #endif
400 }
401
402 //! @brief PROC gamma inc gf = (REAL p, x) REAL
403
404 void genie_gamma_inc_gf_real (NODE_T * q)
405 {
406 // if x <= p: G(p,x) = exp (x-p*ln (|x|)) * integral over [0,|x|] of s^{p-1} * exp (-sign (x)*s) ds
407 // otherwise: G(p,x) = exp (x-p*ln (x)) * integral over [x,inf] of s^{p-1} * exp (-s) ds
408 A68_REAL x, p;
409 POP_OBJECT (q, &x, A68_REAL);
410 POP_OBJECT (q, &p, A68_REAL);
411 REAL_T G;
412 G_func (&G, VALUE (&p), VALUE (&x));
413 PUSH_VALUE (q, G, A68_REAL);
414 }
© 2002-2024 J.M. van der Veer (jmvdveer@xs4all.nl)
|