#include "double_extras.h"
#include "fmpz_poly.h"
#include "fmpq.h"
#include "arb.h"
#include "arb/impl.h"
#include "arb_hypgeom.h"
#include "arb_hypgeom/impl.h"
#include "hypgeom.h"
static void
arb_gamma_const_1_3_eval(arb_t s, slong prec)
{
hypgeom_t series;
arb_t t, u;
slong wp = prec + 4 + 2 * FLINT_BIT_COUNT(prec);
arb_init(t);
arb_init(u);
hypgeom_init(series);
fmpz_poly_set_str(series->A, "2 279 9108");
fmpz_poly_set_str(series->B, "1 1");
fmpz_poly_set_str(series->P, "3 -77 216 -144");
fmpz_poly_set_str(series->Q, "3 0 0 1024000");
prec += FLINT_CLOG2(prec);
arb_hypgeom_infsum(s, t, series, wp, wp);
arb_mul_ui(t, t, 960, wp);
arb_sqrt_ui(u, 10, wp);
arb_sqrt(u, u, wp);
arb_mul(t, t, u, wp);
arb_div(s, t, s, wp);
arb_const_pi(t, wp);
arb_mul(s, s, t, wp);
arb_root_ui(s, s, 3, prec);
hypgeom_clear(series);
arb_clear(t);
arb_clear(u);
}
_ARB_DEF_CACHED_CONSTANT(static, arb_gamma_const_1_3, arb_gamma_const_1_3_eval)
static void
arb_gamma_const_1_4_eval(arb_t x, slong prec)
{
arb_t t, u;
slong wp = prec + 4 + 2 * FLINT_BIT_COUNT(prec);
arb_init(t);
arb_init(u);
arb_one(t);
arb_sqrt_ui(u, 2, wp);
arb_agm(x, t, u, wp);
arb_const_pi(t, wp);
arb_mul_2exp_si(t, t, 1);
arb_sqrt(u, t, wp);
arb_mul(u, u, t, wp);
arb_div(x, u, x, wp);
arb_sqrt(x, x, wp);
arb_clear(t);
arb_clear(u);
}
_ARB_DEF_CACHED_CONSTANT(static, arb_gamma_const_1_4, arb_gamma_const_1_4_eval)
static void
arb_hypgeom_gamma_fmpq_stirling(arb_t y, const fmpq_t a, slong prec)
{
int reflect;
slong r, n, wp;
arb_t x, t, u, v;
wp = (slong) fmpz_bits(fmpq_numref(a)) - (slong) fmpz_bits(fmpq_denref(a));
wp = FLINT_MAX(wp, 0);
wp += prec + FLINT_BIT_COUNT(prec);
arb_init(x);
arb_init(t);
arb_init(u);
arb_init(v);
arb_set_fmpq(x, a, wp);
arb_hypgeom_gamma_stirling_choose_param(&reflect, &r, &n, x, 1, 0, wp);
if (reflect)
{
fmpq_t b;
fmpq_init(b);
fmpz_sub(fmpq_numref(b), fmpq_denref(a), fmpq_numref(a));
fmpz_set(fmpq_denref(b), fmpq_denref(a));
arb_rising_fmpq_ui(u, b, r, wp);
fmpq_clear(b);
arb_const_pi(v, wp);
arb_mul(u, u, v, wp);
arb_sub_ui(t, x, 1, wp);
arb_neg(t, t);
arb_add_ui(t, t, r, wp);
arb_hypgeom_gamma_stirling_inner(v, t, n, wp);
arb_exp(v, v, wp);
arb_sin_pi_fmpq(t, a, wp);
arb_mul(v, v, t, wp);
}
else
{
arb_add_ui(t, x, r, wp);
arb_hypgeom_gamma_stirling_inner(u, t, n, wp);
arb_exp(u, u, prec);
arb_rising_fmpq_ui(v, a, r, wp);
}
arb_div(y, u, v, prec);
arb_clear(t);
arb_clear(u);
arb_clear(v);
arb_clear(x);
}
static void
arb_hypgeom_gamma_small_frac(arb_t y, unsigned int p, unsigned int q, slong prec)
{
slong wp = prec + 4;
if (q == 1)
{
arb_one(y);
}
else if (q == 2)
{
arb_const_sqrt_pi(y, prec);
}
else if (q == 3)
{
if (p == 1)
{
arb_gamma_const_1_3(y, prec);
}
else
{
arb_t t;
arb_init(t);
arb_gamma_const_1_3(y, wp);
arb_sqrt_ui(t, 3, wp);
arb_mul(y, y, t, wp);
arb_const_pi(t, wp);
arb_div(y, t, y, prec);
arb_mul_2exp_si(y, y, 1);
arb_clear(t);
}
}
else if (q == 4)
{
if (p == 1)
{
arb_gamma_const_1_4(y, prec);
}
else
{
arb_t t;
arb_init(t);
arb_sqrt_ui(y, 2, wp);
arb_const_pi(t, wp);
arb_mul(y, y, t, wp);
arb_gamma_const_1_4(t, wp);
arb_div(y, y, t, prec);
arb_clear(t);
}
}
else if (q == 6)
{
arb_t t;
arb_init(t);
arb_const_pi(t, wp);
arb_div_ui(t, t, 3, wp);
arb_sqrt(t, t, wp);
arb_set_ui(y, 2);
arb_root_ui(y, y, 3, wp);
arb_mul(t, t, y, wp);
arb_gamma_const_1_3(y, wp);
arb_mul(y, y, y, prec);
if (p == 1)
{
arb_div(y, y, t, prec);
}
else
{
arb_div(y, t, y, wp);
arb_const_pi(t, wp);
arb_mul(y, y, t, prec);
arb_mul_2exp_si(y, y, 1);
}
arb_clear(t);
}
else
{
flint_throw(FLINT_ERROR, "small fraction not implemented!\n");
}
}
static void
bsplit2(arb_t P, arb_t Q, const fmpz_t zp, const fmpz_t zq,
const slong * xexp, arb_srcptr xpow,
ulong N, slong a, slong b, int cont, slong prec)
{
if (b - a == 1)
{
fmpz_t t;
fmpz_init(t);
fmpz_set(t, zp);
fmpz_addmul_ui(t, zq, a + 1);
arb_set_fmpz(P, t);
arb_set(Q, P);
fmpz_clear(t);
}
else
{
arb_t Pb, Qb;
slong step, i, m;
arb_init(Pb);
arb_init(Qb);
step = (b - a) / 2;
m = a + step;
bsplit2(P, Q, zp, zq, xexp, xpow, N, a, m, 1, prec);
bsplit2(Pb, Qb, zp, zq, xexp, xpow, N, m, b, 1, prec);
arb_mul(P, P, Pb, prec);
arb_mul(Q, Q, Pb, prec);
i = (step == 1) ? 0 : _arb_get_exp_pos(xexp, step);
arb_addmul(Q, Qb, xpow + i, prec);
arb_clear(Pb);
arb_clear(Qb);
}
}
static void
bsplit3(arb_t P, arb_t Q, const fmpz_t zp, const fmpz_t zq,
const slong * xexp, arb_srcptr xpow,
ulong N, slong a, slong b, int cont, slong prec)
{
if (b - a == 1)
{
fmpz_t t;
fmpz_init(t);
arb_set(P, xpow + 0);
fmpz_set(t, zp);
fmpz_submul_ui(t, zq, a + 1);
arb_set_fmpz(Q, t);
fmpz_clear(t);
}
else
{
arb_t Pb, Qb;
slong step, i, m;
arb_init(Pb);
arb_init(Qb);
step = (b - a) / 2;
m = a + step;
bsplit3(P, Q, zp, zq, xexp, xpow, N, a, m, 1, prec);
bsplit3(Pb, Qb, zp, zq, xexp, xpow, N, m, b, 1, prec);
i = _arb_get_exp_pos(xexp, m - a);
arb_mul(P, P, xpow + i, prec);
if (b - m != m - a)
arb_mul(P, P, xpow + 0, prec);
arb_addmul(P, Q, Pb, prec);
if (cont)
{
arb_mul(Q, Q, Qb, prec);
}
else
{
i = _arb_get_exp_pos(xexp, m - a);
arb_mul(Q, xpow + i, xpow + i, prec);
if (b - m != m - a)
arb_mul(Q, Q, xpow + 0, prec);
}
arb_clear(Pb);
arb_clear(Qb);
}
}
static ulong
more_trailing_zeros(ulong N)
{
ulong bc, N2;
bc = FLINT_BIT_COUNT(N);
if (bc >= 8)
{
N2 = (N >> (bc - 5)) << (bc - 5);
N2 += ((N2 != N) << (bc - 5));
return N2;
}
else
{
return N;
}
}
#define C_LOG2 0.69314718055994530942
#define C_EXP1 2.7182818284590452354
static void
build_bsplit_power_table(arb_ptr xpow, const slong * xexp, slong len, slong prec)
{
slong i;
for (i = 1; i < len; i++)
{
if (xexp[i] == 2 * xexp[i-1])
{
arb_mul(xpow + i, xpow + i - 1, xpow + i - 1, prec);
}
else if (xexp[i] == 2 * xexp[i-2])
{
arb_mul(xpow + i, xpow + i - 2, xpow + i - 2, prec);
}
else if (xexp[i] == 2 * xexp[i-1] + 1)
{
arb_mul(xpow + i, xpow + i - 1, xpow + i - 1, prec);
arb_mul(xpow + i, xpow + i, xpow, prec);
}
else if (xexp[i] == 2 * xexp[i-2] + 1)
{
arb_mul(xpow + i, xpow + i - 2, xpow + i - 2, prec);
arb_mul(xpow + i, xpow + i, xpow, prec);
}
else
{
flint_throw(FLINT_ERROR, "power table has the wrong structure!\n");
}
}
}
static void
arb_hypgeom_gamma_fmpq_general_off1(arb_t res, const fmpq_t z, slong prec)
{
slong wp, N, n, n2, length, length2, wp2;
double alpha;
arb_t P, Q;
slong *xexp, *xexp2;
arb_ptr xpow;
mag_t err, err2;
wp = prec + 30;
alpha = 0.52;
N = alpha * C_LOG2 * wp;
N = more_trailing_zeros(N);
alpha = N / (C_LOG2 * wp);
n = (1 - alpha) / d_lambertw((1 - alpha) / (alpha * C_EXP1)) * C_LOG2 * wp;
wp2 = wp * (1 - alpha);
wp2 = FLINT_MAX(wp2, 30);
n2 = (alpha - 1) / d_lambertw_branch1((alpha - 1) / (alpha * C_EXP1)) * C_LOG2 * wp;
n2 = FLINT_MAX(n2, 2);
mag_init(err);
mag_init(err2);
arb_init(P);
arb_init(Q);
xexp = flint_calloc(2 * FLINT_BITS, sizeof(slong));
xexp2 = flint_calloc(2 * FLINT_BITS, sizeof(slong));
length = _arb_compute_bs_exponents(xexp, n);
length2 = _arb_compute_bs_exponents(xexp2, n2);
xpow = _arb_vec_init(FLINT_MAX(length, length2));
arb_set_fmpz(xpow + 0, fmpq_denref(z));
arb_mul_ui(xpow + 0, xpow + 0, N, wp);
build_bsplit_power_table(xpow, xexp, length, wp);
bsplit2(P, Q, fmpq_numref(z), fmpq_denref(z), xexp, xpow, N, 0, n, 0, wp);
arb_div(P, Q, P, wp);
mag_set_ui(err, N);
mag_pow_ui(err, err, n);
mag_rfac_ui(err2, n);
mag_mul(err, err, err2);
mag_set_ui(err2, N);
mag_div_ui(err2, err2, n);
mag_geom_series(err2, err2, 0);
mag_mul(err, err, err2);
arb_add_error_mag(P, err);
arb_mul_fmpz(P, P, fmpq_denref(z), wp);
arb_div_fmpz(P, P, fmpq_numref(z), wp);
arb_swap(res, P);
build_bsplit_power_table(xpow, xexp2, length2, wp2);
bsplit3(P, Q, fmpq_numref(z), fmpq_denref(z), xexp2, xpow, N, 0, n2, 0, wp2);
arb_div(P, P, Q, wp2);
mag_fac_ui(err, n2);
mag_set_ui_lower(err2, N);
mag_pow_ui_lower(err2, err2, n2);
mag_div(err, err, err2);
arb_add_error_mag(P, err);
arb_div_ui(P, P, N, wp2);
arb_add(res, res, P, wp);
arb_set_ui(Q, N);
arb_pow_fmpq(Q, Q, z, wp);
arb_mul(res, res, Q, wp);
arb_set_si(Q, -N);
arb_exp(Q, Q, wp);
arb_mul(res, res, Q, wp);
_arb_vec_clear(xpow, FLINT_MAX(length, length2));
flint_free(xexp);
flint_free(xexp2);
arb_clear(P);
arb_clear(Q);
mag_clear(err);
mag_clear(err2);
}
static void
arb_hypgeom_gamma_fmpq_hyp(arb_t res, const fmpq_t z, slong prec)
{
fmpq_t t;
fmpq_init(t);
fmpq_add_ui(t, z, 1);
arb_hypgeom_gamma_fmpq_general_off1(res, t, prec);
arb_mul_fmpz(res, res, fmpq_denref(z), prec + 30);
arb_div_fmpz(res, res, fmpq_numref(z), prec);
fmpq_clear(t);
}
static void
arb_hypgeom_gamma_fmpq_outward(arb_t y, const fmpq_t x, slong prec)
{
fmpq_t a;
fmpz_t n;
fmpz p, q;
slong m;
arb_t t, u;
fmpq_init(a);
fmpz_init(n);
arb_init(t);
arb_init(u);
if (fmpz_is_one(fmpq_denref(x)))
{
fmpq_one(a);
fmpz_sub_ui(n, fmpq_numref(x), 1);
}
else
{
fmpz_fdiv_qr(n, fmpq_numref(a), fmpq_numref(x), fmpq_denref(x));
fmpz_set(fmpq_denref(a), fmpq_denref(x));
}
if (!fmpz_fits_si(n))
{
flint_throw(FLINT_ERROR, "gamma: too large fmpq to reduce to 0!\n");
}
m = fmpz_get_si(n);
p = *fmpq_numref(a);
q = *fmpq_denref(a);
if (q == 1 || q == 2 || q == 3 || q == 4 || q == 6)
{
arb_hypgeom_gamma_small_frac(t, p, q, prec + 4 * (m != 0));
}
else
{
arb_hypgeom_gamma_fmpq_hyp(t, a, prec + 4 * (m != 0));
}
if (m >= 0)
{
arb_rising_fmpq_ui(u, a, m, prec + 4);
arb_mul(y, t, u, prec);
}
else
{
arb_rising_fmpq_ui(u, x, -m, prec + 4);
arb_div(y, t, u, prec);
}
fmpq_clear(a);
fmpz_clear(n);
arb_clear(t);
arb_clear(u);
}
static int
arb_hypgeom_gamma_fmpq_taylor(arb_t y, const fmpq_t x, slong prec)
{
fmpq_t a;
fmpz_t n;
slong m;
arb_t t;
int success;
fmpq_init(a);
fmpz_init(n);
arb_init(t);
success = 1;
if (fmpz_is_one(fmpq_denref(x)))
{
fmpq_one(a);
fmpz_sub_ui(n, fmpq_numref(x), 1);
}
else
{
fmpz_fdiv_qr(n, fmpq_numref(a), fmpq_numref(x), fmpq_denref(x));
fmpz_set(fmpq_denref(a), fmpq_denref(x));
}
if (!fmpz_fits_si(n))
{
success = 0;
goto cleanup;
}
m = fmpz_get_si(n);
if (m < -(40 + (prec - 40) / 4))
{
success = 0;
goto cleanup;
}
if (m > 70 + (prec - 40) / 8)
{
success = 0;
goto cleanup;
}
arb_set_fmpq(t, a, prec + 4);
success = arb_hypgeom_gamma_taylor(t, t, 0, prec + 4);
if (success)
{
arb_t u;
arb_init(u);
if (m >= 0)
{
arb_rising_fmpq_ui(u, a, m, prec + 4);
arb_mul(y, t, u, prec);
}
else
{
arb_rising_fmpq_ui(u, x, -m, prec + 4);
arb_div(y, t, u, prec);
}
arb_clear(u);
}
cleanup:
fmpq_clear(a);
fmpz_clear(n);
arb_clear(t);
return success;
}
void
arb_hypgeom_gamma_fmpq(arb_t y, const fmpq_t x, slong prec)
{
fmpz p, q;
p = *fmpq_numref(x);
q = *fmpq_denref(x);
if ((q == 1 || q == 2 || q == 3 || q == 4 || q == 6) && !COEFF_IS_MPZ(p))
{
if (q == 1)
{
if (p <= 0)
{
arb_indeterminate(y);
return;
}
if (p < 1200 || 1.44265 * (p*log(p) - p) < 15.0 * prec)
{
fmpz_t t;
fmpz_init(t);
fmpz_fac_ui(t, p - 1);
arb_set_round_fmpz(y, t, prec);
fmpz_clear(t);
return;
}
}
p = FLINT_ABS(p);
if (p < q * 500.0 || p < q * (500.0 + 0.1 * prec * sqrt(prec)))
{
arb_hypgeom_gamma_fmpq_outward(y, x, prec);
return;
}
}
if (q != 1 && prec > 7000 + 300 * fmpz_bits(fmpq_denref(x)) &&
(slong) fmpz_bits(fmpq_numref(x)) - (slong) fmpz_bits(fmpq_denref(x)) < FLINT_BITS &&
fabs(fmpq_get_d(x)) < 0.03 * prec * sqrt(prec))
{
arb_hypgeom_gamma_fmpq_outward(y, x, prec);
return;
}
if (fmpz_bits(fmpq_denref(x)) > 0.1 * prec || fmpz_bits(fmpq_numref(x)) > 0.1 * prec)
{
slong wp;
wp = (slong) fmpz_bits(fmpq_numref(x)) - (slong) fmpz_bits(fmpq_denref(x));
wp = FLINT_MAX(wp, 0);
wp = FLINT_MIN(wp, 10 * prec);
wp += prec + 4;
arb_set_fmpq(y, x, wp);
if (!arb_hypgeom_gamma_taylor(y, y, 0, prec))
arb_hypgeom_gamma_stirling(y, y, 0, prec);
return;
}
if (arb_hypgeom_gamma_fmpq_taylor(y, x, prec))
return;
arb_hypgeom_gamma_fmpq_stirling(y, x, prec);
}
void
arb_hypgeom_gamma_fmpz(arb_t y, const fmpz_t x, slong prec)
{
fmpq_t t;
*fmpq_numref(t) = *x;
*fmpq_denref(t) = WORD(1);
arb_hypgeom_gamma_fmpq(y, t, prec);
}