diff --git a/acb_dirichlet.h b/acb_dirichlet.h index df353a91..63d3edb6 100644 --- a/acb_dirichlet.h +++ b/acb_dirichlet.h @@ -165,6 +165,8 @@ void acb_dirichlet_hardy_theta_series(acb_poly_t res, const acb_poly_t s, const void _acb_dirichlet_hardy_z_series(acb_ptr res, acb_srcptr s, slong slen, const dirichlet_group_t G, const dirichlet_char_t chi, slong len, slong prec); void acb_dirichlet_hardy_z_series(acb_poly_t res, const acb_poly_t s, const dirichlet_group_t G, const dirichlet_char_t chi, slong len, slong prec); +void acb_dirichlet_gram_point(arb_t res, const fmpz_t n, const dirichlet_group_t G, const dirichlet_char_t chi, slong prec); + /* Discrete Fourier Transform */ void acb_dirichlet_dft_index(acb_ptr w, acb_srcptr v, const dirichlet_group_t G, slong prec); diff --git a/acb_dirichlet/gram_point.c b/acb_dirichlet/gram_point.c new file mode 100644 index 00000000..950dc93d --- /dev/null +++ b/acb_dirichlet/gram_point.c @@ -0,0 +1,174 @@ +/* + Copyright (C) 2019 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 . +*/ + +#include "acb_dirichlet.h" + +/* +Claim: the error is bounded by 1/256 if n <= 1 and (1/64) (log(n)/n) if n >= 2. + +A crude lower bound for g_n is 2 pi exp(W(n)), or 8*n/log(n) for n >= 8. + +We want to solve pi n = -t/2 log(2 pi/t) - t/2 - pi/8 + epsilon for t (= g_n). +Using (47) in Brent [https://arxiv.org/abs/1609.03682], |epsilon| <= 1/(8 t) for for t >= 2. +Also, for x >= 3, |f'(x)| < 0.5 where f(x) = exp(W(x)). + +Assume n >= 9, so that (n+1/8)/e >= 3.35. Then inverting gives + +t = 2 pi exp[W( [pi n - epsilon + pi/8] / (pi e) ) + 1] + = 2 pi e exp[W((n+1/8)/e - epsilon / (pi e))] + = 2 pi e exp[W((n+1/8)/e)] + epsilon2, |epsilon2| <= 1/(8 t) <= (1/64) (log(n)/n) + +One can check 0 <= n <= 8 separately. +*/ +static void +gram_point_initial(arb_t x, const fmpz_t n, slong prec) +{ + arb_t pi, e; + mag_t b; + + arb_init(pi); + arb_init(e); + mag_init(b); + + arb_const_pi(pi, prec); + arb_const_e(e, prec); + + /* x = 2*pi*exp(1 + W((n+1/8)/e)) */ + arb_one(x); + arb_mul_2exp_si(x, x, -3); + arb_add_fmpz(x, x, n, prec); + arb_div(x, x, e, prec); + arb_lambertw(x, x, 0, prec); + arb_add_ui(x, x, 1, prec); + arb_exp(x, x, prec); + arb_mul(x, x, pi, prec); + arb_mul_2exp_si(x, x, 1); + + if (fmpz_cmp_ui(n, 1) <= 0) + { + mag_set_ui_2exp_si(b, 1, -8); + } + else + { + mag_set_fmpz(b, n); + mag_log(b, b); + mag_div_fmpz(b, b, n); + mag_mul_2exp_si(b, b, -6); + } + + arb_add_error_mag(x, b); + + arb_clear(pi); + arb_clear(e); + mag_clear(b); +} + +void +acb_dirichlet_gram_point(arb_t res, const fmpz_t n, const dirichlet_group_t G, const dirichlet_char_t chi, slong prec) +{ + slong asymp_accuracy; + + /* Only implemented for n >= 0 and Riemann zeta. */ + if (fmpz_sgn(n) < 0 || G != NULL || chi != NULL) + { + arb_indeterminate(res); + return; + } + + asymp_accuracy = 2 * fmpz_bits(n); + asymp_accuracy = FLINT_MIN(asymp_accuracy, prec); + + gram_point_initial(res, n, asymp_accuracy + 20); + asymp_accuracy = arb_rel_accuracy_bits(res); + + if (asymp_accuracy < prec) + { + acb_struct tmp[2]; + arb_t f, fprime, root; + mag_t C, r; + slong * steps; + slong wp, step; + + acb_init(tmp); + acb_init(tmp + 1); + arb_init(f); + arb_init(fprime); + arb_init(root); + mag_init(C); + mag_init(r); + steps = flint_malloc(sizeof(slong) * FLINT_BITS); + + step = 0; + steps[step] = prec * 1.05 + 10; + + while (steps[step] / 2 > asymp_accuracy) + { + steps[step + 1] = steps[step] / 2; + step++; + } + + arb_set(root, res); + + /* theta''(x) <= C = 1/x, x >= 1 */ + arb_get_mag_lower(C, root); + if (mag_cmp_2exp_si(C, 0) >= 0) + mag_inv(C, C); + else + mag_inf(C); + + arb_set(root, res); + + for ( ; step >= 0; step--) + { + wp = steps[step] + 10; + wp = FLINT_MAX(wp, arb_rel_accuracy_bits(root) + 10); + + /* store radius, set root to the midpoint */ + mag_set(r, arb_radref(root)); + mag_zero(arb_radref(root)); + + acb_set_arb(tmp, root); + acb_dirichlet_hardy_theta(tmp, tmp, NULL, NULL, 2, wp); + arb_set(f, acb_realref(tmp)); + arb_const_pi(acb_imagref(tmp), wp); + arb_submul_fmpz(f, acb_imagref(tmp), n, wp); + + arb_set(fprime, acb_realref(tmp + 1)); + + /* f'([m+/-r]) = f'(m) +/- f''([m +/- r]) * r */ + mag_mul(r, C, r); + arb_add_error_mag(fprime, r); + arb_div(f, f, fprime, wp); + arb_sub(root, root, f, wp); + + /* Verify inclusion so that C is still valid. */ + if (!arb_contains(res, root)) + { + flint_printf("unexpected: no containment computing Gram point\n"); + arb_set(root, res); + break; + } + } + + arb_set(res, root); + + acb_clear(tmp); + acb_clear(tmp + 1); + arb_clear(f); + arb_clear(fprime); + arb_clear(root); + mag_clear(C); + mag_clear(r); + flint_free(steps); + } + + arb_set_round(res, res, prec); +} diff --git a/acb_dirichlet/test/t-gram_point.c b/acb_dirichlet/test/t-gram_point.c new file mode 100644 index 00000000..a82a6d9f --- /dev/null +++ b/acb_dirichlet/test/t-gram_point.c @@ -0,0 +1,94 @@ +/* + Copyright (C) 2019 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 . +*/ + +#include "acb_dirichlet.h" + +int main() +{ + slong iter; + flint_rand_t state; + + flint_printf("gram_point...."); + fflush(stdout); + + flint_randinit(state); + + for (iter = 0; iter < 1000 * arb_test_multiplier(); iter++) + { + arb_t x1, x2, v1, v2, pin; + acb_t t; + fmpz_t n; + slong prec1, prec2; + + arb_init(x1); + arb_init(x2); + arb_init(v1); + arb_init(v2); + arb_init(pin); + acb_init(t); + fmpz_init(n); + + fmpz_randtest(n, state, 500); + fmpz_abs(n, n); + prec1 = 2 + n_randtest(state) % 500; + prec2 = 2 + n_randtest(state) % 2000; + + acb_dirichlet_gram_point(x1, n, NULL, NULL, prec1); + acb_dirichlet_gram_point(x2, n, NULL, NULL, prec2); + + arb_const_pi(pin, FLINT_MAX(prec1, prec2) + 20); + arb_mul_fmpz(pin, pin, n, FLINT_MAX(prec1, prec2) + 20); + + acb_set_arb(t, x1); + acb_dirichlet_hardy_theta(t, t, NULL, NULL, 1, prec1 + 20); + arb_set(v1, acb_realref(t)); + + acb_set_arb(t, x2); + acb_dirichlet_hardy_theta(t, t, NULL, NULL, 1, prec2 + 20); + arb_set(v2, acb_realref(t)); + + if (!arb_overlaps(x1, x2) || !arb_contains(v1, pin) || !arb_contains(v2, pin)) + { + flint_printf("FAIL: overlap\n\n"); + flint_printf("n = "); fmpz_print(n); + flint_printf(" prec1 = %wd prec2 = %wd\n\n", prec1, prec2); + flint_printf("x1 = "); arb_printn(x1, 100, 0); flint_printf("\n\n"); + flint_printf("x2 = "); arb_printn(x2, 100, 0); flint_printf("\n\n"); + flint_printf("v1 = "); arb_printn(v1, 100, 0); flint_printf("\n\n"); + flint_printf("v2 = "); arb_printn(v2, 100, 0); flint_printf("\n\n"); + flint_abort(); + } + + if (arb_rel_accuracy_bits(x1) < prec1 - 3 || arb_rel_accuracy_bits(x2) < prec2 - 3) + { + flint_printf("FAIL: accuracy\n\n"); + flint_printf("n = "); fmpz_print(n); + flint_printf(" prec1 = %wd prec2 = %wd\n\n", prec1, prec2); + flint_printf("acc(x1) = %wd, acc(x2) = %wd\n\n", arb_rel_accuracy_bits(x1), arb_rel_accuracy_bits(x2)); + flint_printf("x1 = "); arb_printn(x1, 100, 0); flint_printf("\n\n"); + flint_printf("x2 = "); arb_printn(x2, 100, 0); flint_printf("\n\n"); + flint_abort(); + } + + arb_clear(x1); + arb_clear(x2); + arb_clear(v1); + arb_clear(v2); + arb_clear(pin); + acb_clear(t); + fmpz_clear(n); + } + + flint_randclear(state); + flint_cleanup(); + flint_printf("PASS\n"); + return EXIT_SUCCESS; +} diff --git a/doc/source/acb_dirichlet.rst b/doc/source/acb_dirichlet.rst index 7c8f0705..173a6f03 100644 --- a/doc/source/acb_dirichlet.rst +++ b/doc/source/acb_dirichlet.rst @@ -613,3 +613,10 @@ Currently, these methods require *chi* to be a primitive character. Sets *res* to the power series `Z(t)` where *t* is a given power series, truncating the result to length *len*. +.. function:: void acb_dirichlet_gram_point(arb_t res, const fmpz_t n, const dirichlet_group_t G, const dirichlet_char_t chi, slong prec) + + Sets *res* to the *n*-th Gram point `g_n`, defined as the solution of + `\theta(g_n) = \pi n`. Currently only the Gram points corresponding to the + Riemann zeta function are supported and *G* and *chi* must both be set to + *NULL*. Requires `n \ge 0`. +