arb/arb_hypgeom/gamma.c
2021-09-02 12:53:03 +02:00

416 lines
9.8 KiB
C

/*
Copyright (C) 2021 Fredrik Johansson
This file is part of Arb.
Arb is free software: you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License (LGPL) as published
by the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version. See <http://www.gnu.org/licenses/>.
*/
#include "arb_hypgeom.h"
#include "bernoulli.h"
/* tuning factor */
double GAMMA_STIRLING_BETA = 0.0;
#define PI 3.1415926535897932385
static slong
choose_n(double log2z, double argz, int digamma, slong prec)
{
double argf, boundn, boundn_best;
slong n, nbest;
argf = 1.0 / cos(0.5 * argz);
argf = log(argf) * (1. / log(2));
boundn_best = 1e300;
nbest = 1;
for (n = 1; ; n++)
{
if (digamma)
boundn = bernoulli_bound_2exp_si(2*n) - (2*n)*log2z + (2*n+1)*argf;
else
boundn = bernoulli_bound_2exp_si(2*n) - (2*n-1)*log2z + (2*n)*argf;
/* success */
if (boundn <= -prec)
return n;
if (boundn < boundn_best)
{
nbest = n;
boundn_best = boundn;
}
/* if the term magnitude does not decrease, r is too small */
if (boundn > 1)
{
/* printf("failure: prec = %ld, nbound_best = %f [%ld, %ld]\n", prec, boundn_best, n, nbest); */
return nbest;
}
}
}
static void
choose_small(int * reflect, slong * r, slong * n,
double x, double y, int use_reflect, int digamma, slong prec)
{
double w, argz, log2z, BETA;
slong rr;
/* use reflection formula if very negative */
if (x < -5.0 && use_reflect)
{
*reflect = 1;
x = 1.0 - x;
}
else
{
*reflect = 0;
}
BETA = GAMMA_STIRLING_BETA;
if (BETA < 0.12)
{
if (prec <= 32768)
BETA = 0.17;
else if (prec <= 131072)
BETA = 0.20;
else
BETA = 0.24;
}
/* argument reduction until |z| >= w */
w = FLINT_MAX(1.0, BETA * prec);
rr = 0;
while (x < 1.0 || x*x + y*y < w*w)
{
x++;
rr++;
}
log2z = 0.5 * log(x*x + y*y) * 1.44269504088896341;
argz = atan2(y, x);
*r = rr;
*n = choose_n(log2z, argz, digamma, prec);
}
static void
choose_large(int * reflect, slong * r, slong * n,
const arf_t a, const arf_t b, int use_reflect, int digamma, slong prec)
{
if (use_reflect && arf_sgn(a) < 0)
*reflect = 1;
else
*reflect = 0;
*r = 0;
/* so big that we will certainly have n = 0 */
if (arf_cmpabs_2exp_si(a, WORD_MAX / 8) >= 0 ||
arf_cmpabs_2exp_si(b, WORD_MAX / 8) >= 0)
{
*n = 0;
}
else
{
slong ab, bb;
double log2z, argz;
ab = arf_abs_bound_lt_2exp_si(a);
bb = arf_abs_bound_lt_2exp_si(b);
log2z = FLINT_MAX(ab, bb);
/* piecewise approximation of the argument */
if (arf_is_zero(b))
{
if ((arf_sgn(a) < 0) && !(*reflect))
argz = PI;
else
argz = 0.0;
}
else
{
if ((arf_sgn(a) < 0) && !(*reflect))
if (arf_cmpabs(a, b) <= 0)
argz = PI * 0.75;
else
argz = PI;
else
if (arf_cmpabs(a, b) <= 0)
argz = PI * 0.25;
else
argz = PI * 0.5;
}
if (argz == PI)
*n = 0;
else
*n = choose_n(log2z, argz, digamma, prec);
}
}
void
acb_hypgeom_gamma_stirling_choose_param(int * reflect, slong * r, slong * n,
const acb_t z, int use_reflect, int digamma, slong prec)
{
const arf_struct * a = arb_midref(acb_realref(z));
const arf_struct * b = arb_midref(acb_imagref(z));
if (!arf_is_finite(a) || !arf_is_finite(b))
{
*reflect = *r = *n = 0;
}
else if (arf_cmpabs_2exp_si(a, 40) > 0 || arf_cmpabs_2exp_si(b, 40) > 0)
{
choose_large(reflect, r, n, a, b, use_reflect, digamma, prec);
}
else
{
choose_small(reflect, r, n,
arf_get_d(a, ARF_RND_UP),
arf_get_d(b, ARF_RND_UP), use_reflect, digamma, prec);
}
}
void
arb_hypgeom_gamma_stirling_choose_param(int * reflect, slong * r, slong * n,
const arb_t x, int use_reflect, int digamma, slong prec)
{
const arf_struct * a = arb_midref(x);
if (arf_is_inf(a) || arf_is_nan(a))
{
*reflect = *r = *n = 0;
}
else if (arf_cmpabs_2exp_si(a, 40) > 0)
{
arf_t b;
arf_init(b);
choose_large(reflect, r, n, a, b, use_reflect, digamma, prec);
arf_clear(b);
}
else
{
choose_small(reflect, r, n,
arf_get_d(a, ARF_RND_UP), 0.0, use_reflect, digamma, prec);
}
}
void arb_gamma_stirling_bound(mag_ptr err, const arb_t x, slong k0, slong knum, slong n);
void
arb_hypgeom_gamma_stirling_inner(arb_t s, const arb_t z, slong N, slong prec)
{
arb_t logz, t;
mag_t err;
mag_init(err);
arb_init(t);
arb_init(logz);
arb_gamma_stirling_bound(err, z, 0, 1, N);
/* t = (z-0.5)*log(z) - z + log(2*pi)/2 */
arb_log(logz, z, prec);
arb_one(t);
arb_mul_2exp_si(t, t, -1);
arb_sub(t, z, t, prec);
arb_mul(t, logz, t, prec);
arb_sub(t, t, z, prec);
arb_const_log_sqrt2pi(logz, prec);
arb_add(t, t, logz, prec);
/* sum part */
if (prec <= 128 || (prec <= 768 && N <= 40) || (prec <= 2048 && N <= 16))
arb_hypgeom_gamma_stirling_sum_horner(s, z, N, prec);
else
arb_hypgeom_gamma_stirling_sum_improved(s, z, N, 0, prec);
arb_add(s, s, t, prec);
mag_add(arb_radref(s), arb_radref(s), err);
arb_clear(t);
arb_clear(logz);
mag_clear(err);
}
int
arb_hypgeom_gamma_exact(arb_t res, const arb_t x, int reciprocal, slong prec)
{
if (arb_is_exact(x))
{
const arf_struct * mid = arb_midref(x);
if (arf_is_special(mid))
{
if (!reciprocal && arf_is_pos_inf(mid))
arb_set(res, x);
else if (arf_is_nan(mid) || arf_is_neg_inf(mid) || !reciprocal)
arb_indeterminate(res);
else
arb_zero(res);
return 1;
}
else if (reciprocal && arf_is_int(mid) && arf_sgn(mid) < 0)
{
arb_zero(res);
return 1;
}
else
{
/* todo: cutoffs for larger denominators */
/* fast gamma(n), gamma(n/2) or gamma(n/4), ... */
if (arf_cmpabs_2exp_si(mid, prec) < 0 &&
(arf_is_int_2exp_si(mid, -2) || (prec > 1000 && arf_is_int_2exp_si(mid, -prec / 50))))
{
fmpq_t a;
fmpq_init(a);
arf_get_fmpq(a, mid);
arb_gamma_fmpq(res, a, prec + 2 * reciprocal);
if (reciprocal)
arb_inv(res, res, prec);
fmpq_clear(a);
return 1;
}
}
}
return 0;
}
void
arb_hypgeom_gamma_stirling(arb_t y, const arb_t x, int reciprocal, slong prec)
{
int reflect;
slong r, n, wp;
arb_t t, u, v;
double acc;
/* todo: for large x (if exact or accurate enough), increase precision */
acc = arb_rel_accuracy_bits(x);
acc = FLINT_MAX(acc, 0);
wp = FLINT_MIN(prec, acc + 20);
wp = FLINT_MAX(wp, 2);
wp = wp + FLINT_BIT_COUNT(wp);
if (acc < 3) /* try to avoid divisions blowing up */
{
if (arf_cmp_d(arb_midref(x), -0.5) < 0)
{
reflect = 1;
r = 0;
}
else if (arf_cmp_si(arb_midref(x), 1) < 0)
{
reflect = 0;
r = 1;
}
else
{
reflect = 0;
r = 0;
}
n = 1;
}
else
{
arb_hypgeom_gamma_stirling_choose_param(&reflect, &r, &n, x, 1, 0, wp);
}
arb_init(t);
arb_init(u);
arb_init(v);
if (reflect)
{
arb_sub_ui(t, x, 1, wp);
arb_neg(t, t);
arb_hypgeom_rising_ui_rec(u, t, r, wp);
arb_const_pi(v, wp);
arb_mul(u, u, v, wp);
arb_add_ui(t, t, r, wp);
arb_hypgeom_gamma_stirling_inner(v, t, n, wp);
if (reciprocal)
{
/* rgamma(x) = gamma(1-x+r) sin(pi x) / ((rf(1-x, r) * pi) */
arb_exp(v, v, wp);
arb_sin_pi(t, x, wp);
arb_mul(v, v, t, wp);
arb_mul(y, u, v, wp);
arb_div(y, v, u, prec);
}
else
{
/* gamma(x) = (rf(1-x, r) * pi) rgamma(1-x+r) csc(pi x) */
arb_neg(v, v);
arb_exp(v, v, wp);
arb_csc_pi(t, x, wp);
arb_mul(v, v, t, wp);
arb_mul(y, v, u, prec);
}
}
else
{
arb_add_ui(t, x, r, wp);
arb_hypgeom_gamma_stirling_inner(u, t, n, wp);
if (reciprocal)
{
/* rgamma(x) = rf(x,r) rgamma(x+r) */
arb_neg(u, u);
arb_exp(u, u, prec);
arb_hypgeom_rising_ui_rec(v, x, r, wp);
arb_mul(y, v, u, prec);
}
else
{
/* gamma(x) = gamma(x+r) / rf(x,r) */
arb_exp(u, u, prec);
arb_hypgeom_rising_ui_rec(v, x, r, wp);
arb_div(y, u, v, prec);
}
}
arb_clear(t);
arb_clear(u);
arb_clear(v);
}
void
arb_hypgeom_gamma(arb_t y, const arb_t x, slong prec)
{
if (arb_hypgeom_gamma_exact(y, x, 0, prec))
return;
if (arb_hypgeom_gamma_taylor(y, x, 0, prec))
return;
arb_hypgeom_gamma_stirling(y, x, 0, prec);
}
void
arb_hypgeom_rgamma(arb_t y, const arb_t x, slong prec)
{
if (arb_hypgeom_gamma_exact(y, x, 1, prec))
return;
if (arb_hypgeom_gamma_taylor(y, x, 1, prec))
return;
arb_hypgeom_gamma_stirling(y, x, 1, prec);
}