/* Copyright (C) 2019 D.H.J Polymath 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" #include "arb_hypgeom.h" #include "arb_poly.h" static void _arb_div_si_si(arb_t res, slong x, slong y, slong prec) { arb_set_si(res, x); arb_div_si(res, res, y, prec); } static void _arb_ui_pow_arb(arb_t res, ulong n, const arb_t x, slong prec) { arb_t a; arb_init(a); arb_set_ui(a, n); arb_pow(res, a, x, prec); arb_clear(a); } /* * Follows the notation in https://en.wikipedia.org/wiki/Gaussian_function * res = a*exp(-(x-b)^2 / (2*c^2)) * If 'a' is NULL a default coefficient of 1 is used. * If 'b' is NULL a default mean of 0 is used. */ static void _arb_gaussian(arb_t res, const arb_t a, const arb_t b, const arb_t c, const arb_t x, slong prec) { arb_t z; arb_init(z); if (b == NULL) arb_set(z, x); else arb_sub(z, x, b, prec); arb_div(z, z, c, prec); arb_sqr(z, z, prec); arb_mul_2exp_si(z, z, -1); arb_neg(z, z); arb_exp(z, z, prec); if (a == NULL) arb_set(res, z); else arb_mul(res, z, a, prec); arb_clear(z); } static void _platt_lambda(arb_t res, const arb_t t, slong prec) { acb_t pi, s, z, s1, s2; acb_init(pi); acb_init(s); acb_init(z); acb_init(s1); acb_init(s2); arb_set_d(acb_realref(s), 0.5); arb_set(acb_imagref(s), t); acb_const_pi(pi, prec); arb_mul_2exp_si(acb_imagref(s1), t, -1); acb_neg(s1, s1); acb_pow(s1, pi, s1, prec); acb_mul_2exp_si(s2, s, -1); acb_gamma(s2, s2, prec); acb_zeta(z, s, prec); acb_mul(z, z, s1, prec); acb_mul(z, z, s2, prec); if (!arb_contains_zero(acb_imagref(z))) { flint_abort(); } arb_set(res, acb_realref(z)); acb_clear(pi); acb_clear(s); acb_clear(z); acb_clear(s1); acb_clear(s2); } void acb_dirichlet_platt_scaled_lambda(arb_t res, const arb_t t, slong prec) { arb_t pi, lam; arb_init(pi); arb_init(lam); arb_const_pi(pi, prec); _platt_lambda(lam, t, prec); arb_mul(res, pi, t, prec); arb_mul_2exp_si(res, res, -2); arb_exp(res, res, prec); arb_mul(res, res, lam, prec); arb_clear(pi); arb_clear(lam); } void acb_dirichlet_platt_scaled_lambda_vec(arb_ptr res, const fmpz_t T, slong A, slong B, slong prec) { slong N = A*B; if (A < 1 || B < 1 || N % 2) { flint_printf("requires an even number of grid points\n"); flint_abort(); } else { slong i; arb_t t; arb_init(t); for (i = 0; i < N; i++) { slong n = i - N/2; _arb_div_si_si(t, n, A, prec); arb_add_fmpz(t, t, T, prec); acb_dirichlet_platt_scaled_lambda(res + i, t, prec); } arb_clear(t); } } static void _platt_bound_C3_X(arb_t res, const arb_t t0, slong A, const arb_t H, slong Ns, const arb_t beta, slong prec) { arb_t a, c, x; arb_init(a); arb_init(c); arb_init(x); /* res = gaussian(a=(t0+Ns/A)^beta, b=NULL, c=A*H, x=Ns) */ _arb_div_si_si(a, Ns, A, prec); arb_add(a, a, t0, prec); arb_pow(a, a, beta, prec); arb_mul_si(c, H, A, prec); arb_set_si(x, Ns); _arb_gaussian(res, a, NULL, c, x, prec); arb_clear(a); arb_clear(c); arb_clear(x); } static void _platt_bound_C3_Y(arb_t res, const arb_t t0, slong A, const arb_t H, slong Ns, const arb_t beta, slong prec) { arb_t a, b, g, g1, g2; arb_init(a); arb_init(b); arb_init(g); arb_init(g1); arb_init(g2); /* a = 2^((2*beta - 1)/2) */ arb_mul_2exp_si(a, beta, 1); arb_sub_ui(a, a, 1, prec); arb_mul_2exp_si(a, a, -1); _arb_ui_pow_arb(a, 2, a, prec); /* b = t0^beta */ arb_pow(b, t0, beta, prec); /* g = incomplete_gamma(1/2, Ns^2 / (2*A^2*H^2)) */ arb_set_d(g1, 0.5); _arb_div_si_si(g2, Ns, A, prec); arb_div(g2, g2, H, prec); arb_sqr(g2, g2, prec); arb_mul_2exp_si(g2, g2, -1); arb_hypgeom_gamma_upper(g, g1, g2, 0, prec); /* res = a*b*A*H*g */ arb_mul_si(res, H, A, prec); arb_mul(res, res, a, prec); arb_mul(res, res, b, prec); arb_mul(res, res, g, prec); arb_clear(a); arb_clear(b); arb_clear(g); arb_clear(g1); arb_clear(g2); } static void _platt_bound_C3_Z(arb_t res, const arb_t t0, slong A, const arb_t H, const arb_t beta, slong prec) { arb_t a, b, g, g1, g2; arb_init(a); arb_init(b); arb_init(g); arb_init(g1); arb_init(g2); /* a = 2^((3*beta - 1)/2) */ arb_mul_ui(a, beta, 3, prec); arb_sub_ui(a, a, 1, prec); arb_mul_2exp_si(a, a, -1); _arb_ui_pow_arb(a, 2, a, prec); /* b = H^(beta + 1) */ arb_add_ui(b, beta, 1, prec); arb_pow(b, H, b, prec); /* g = incomplete_gamma((beta+1)/2, t0^2 / (2*H^2)) */ arb_add_ui(g1, beta, 1, prec); arb_mul_2exp_si(g1, g1, -1); arb_div(g2, t0, H, prec); arb_sqr(g2, g2, prec); arb_mul_2exp_si(g2, g2, -1); arb_hypgeom_gamma_upper(g, g1, g2, 0, prec); /* res = a*b*A*g */ arb_mul_si(res, g, A, prec); arb_mul(res, res, a, prec); arb_mul(res, res, b, prec); arb_clear(a); arb_clear(b); arb_clear(g); arb_clear(g1); arb_clear(g2); } /* * An improvement of Lemma C.3 is used, which is tighter by a factor of A. * https://github.com/fredrik-johansson/arb/pull/269#issuecomment-468488347 */ void acb_dirichlet_platt_bound_C3(arb_t res, const arb_t t0, slong A, const arb_t H, slong Ns, slong prec) { arb_t pi, ee, beta, X, Y, Z, lhs, rhs; arb_init(pi); arb_init(ee); arb_init(beta); arb_init(X); arb_init(Y); arb_init(Z); arb_init(lhs); arb_init(rhs); /* t0 > exp(exp(1) is required for valid error bounds. */ arb_const_e(ee, prec); arb_exp(ee, ee, prec); if (!arb_gt(t0, ee)) { arb_zero_pm_inf(res); goto finish; } /* 0 < Ns <= t0*A is required for valid error bounds. */ arb_set_si(lhs, Ns); arb_mul_si(rhs, t0, A, prec); if (!arb_is_positive(lhs) || !arb_le(lhs, rhs)) { arb_zero_pm_inf(res); goto finish; } /* res = (X + Y + Z) * 6 / (pi * Ns) */ arb_const_pi(pi, prec); acb_dirichlet_platt_beta(beta, t0, prec); _platt_bound_C3_X(X, t0, A, H, Ns, beta, prec); _platt_bound_C3_Y(Y, t0, A, H, Ns, beta, prec); _platt_bound_C3_Z(Z, t0, A, H, beta, prec); arb_add(res, X, Y, prec); arb_add(res, res, Z, prec); arb_mul_ui(res, res, 6, prec); arb_div(res, res, pi, prec); arb_div_si(res, res, Ns, prec); finish: arb_clear(pi); arb_clear(ee); arb_clear(beta); arb_clear(X); arb_clear(Y); arb_clear(Z); arb_clear(lhs); arb_clear(rhs); } /* Does not account for limited resolution and supporting points. */ static void _interpolation_helper_raw(arb_t res, const arb_t t0, arb_srcptr p, const fmpz_t T, slong A, slong B, slong i0, slong Ns, const arb_t H, slong prec) { mag_t m; arb_t accum1; /* sum of terms where the argument of sinc is small */ arb_t accum2; /* sum of terms where the argument of sinc is large */ arb_t dt0, dt, a, b, s, pi, g, x, c; slong i; slong N = A*B; mag_init(m); arb_init(accum1); arb_init(accum2); arb_init(dt0); arb_init(dt); arb_init(a); arb_init(b); arb_init(s); arb_init(pi); arb_init(g); arb_init(x); arb_init(c); arb_const_pi(pi, prec); arb_sub_fmpz(dt0, t0, T, prec + fmpz_clog_ui(T, 2)); /* x = -N/2 - A*dt0 */ arb_mul_si(x, dt0, A, prec); arb_add_si(x, x, N/2, prec); arb_neg(x, x); /* c = sin(pi*x) / pi */ arb_sin_pi(c, x, prec); arb_div(c, c, pi, prec); for (i = i0; i < i0 + 2*Ns; i++) { slong n = i - N/2; _arb_div_si_si(dt, n, A, prec); _arb_gaussian(g, NULL, dt0, H, dt, prec); arb_mul(s, g, p + i, prec); arb_add_si(a, x, i, prec); arb_get_mag(m, a); if (mag_cmp_2exp_si(m, -1) < 0) { arb_sinc_pi(b, a, prec); arb_addmul(accum1, s, b, prec); } else { arb_div(b, s, a, prec); if (i % 2) { arb_neg(b, b); } arb_add(accum2, accum2, b, prec); } } arb_set(res, accum1); arb_addmul(res, accum2, c, prec); mag_clear(m); arb_clear(accum1); arb_clear(accum2); arb_clear(dt0); arb_clear(dt); arb_clear(a); arb_clear(b); arb_clear(s); arb_clear(pi); arb_clear(g); arb_clear(x); arb_clear(c); } /* Sets res to the function (a * exp(-(b-h)^2 / c)) * sinc_pi(d*(b-h))) * of the power series h, for the purpose of computing derivatives * of the Gaussian-windowed Whittaker-Shannon interpolation. * Supports aliasing. */ static void _arb_poly_gwws_series(arb_ptr res, arb_srcptr h, slong hlen, const arb_t a, const arb_t b, const arb_t c, const arb_t d, slong len, slong prec) { arb_ptr u, u2, v, w; hlen = FLINT_MIN(hlen, len); u = _arb_vec_init(hlen); u2 = _arb_vec_init(len); v = _arb_vec_init(len); w = _arb_vec_init(len); /* u = b-h; u2 = (b-h)^2 */ _arb_vec_neg(u, h, hlen); arb_add(u, u, b, prec); _arb_poly_mullow(u2, u, hlen, u, hlen, len, prec); /* v = exp(-(b-h)^2 / c) */ _arb_vec_scalar_div(v, u2, len, c, prec); _arb_vec_neg(v, v, len); _arb_poly_exp_series(v, v, len, len, prec); /* w = sinc_pi(d*(b-h)) */ _arb_vec_scalar_mul(w, u, hlen, d, prec); _arb_poly_sinc_pi_series(w, w, hlen, len, prec); /* res = a * exp(-(b-h)^2 / c)) * sinc_pi(d*(b-h)) */ _arb_poly_mullow(res, v, len, w, len, len, prec); _arb_vec_scalar_mul(res, res, len, a, prec); _arb_vec_clear(u, hlen); _arb_vec_clear(u2, len); _arb_vec_clear(v, len); _arb_vec_clear(w, len); } /* Does not account for limited resolution and supporting points. */ static void _interpolation_helper_raw_series(arb_ptr res, arb_srcptr t0, slong t0len, arb_srcptr p, const fmpz_t T, slong A, slong B, slong i0, slong Ns, const arb_t H, slong trunc, slong prec) { t0len = FLINT_MIN(t0len, trunc); if (t0len == 1) { _interpolation_helper_raw(res, t0, p, T, A, B, i0, Ns, H, prec); _arb_vec_zero(res + 1, trunc - 1); } else { arb_ptr h, g, accum; arb_t b, c, d; slong N = A*B; slong i; arb_init(b); arb_init(c); arb_init(d); h = _arb_vec_init(t0len); g = _arb_vec_init(trunc); accum = _arb_vec_init(trunc); arb_sqr(c, H, prec); arb_mul_2exp_si(c, c, 1); arb_set_si(d, A); _arb_vec_set(h, t0, t0len); arb_sub_fmpz(h, t0, T, prec + fmpz_clog_ui(T, 2)); for (i = i0; i < i0 + 2*Ns; i++) { slong n = i - N/2; _arb_div_si_si(b, n, A, prec); _arb_poly_gwws_series(g, h, t0len, p + i, b, c, d, trunc, prec); _arb_vec_add(accum, accum, g, trunc, prec); } _arb_vec_set(res, accum, trunc); arb_clear(b); arb_clear(c); arb_clear(d); _arb_vec_clear(h, t0len); _arb_vec_clear(g, trunc); _arb_vec_clear(accum, trunc); } } static void _interpolation_deriv_helper(arf_t res, const arb_t t0, arb_srcptr p, const fmpz_t T, slong A, slong B, slong i0, slong Ns, const arb_t H, slong prec) { arb_ptr t, h; t = _arb_vec_init(2); h = _arb_vec_init(2); arb_set(t+0, t0); arb_one(t+1); _interpolation_helper_raw_series( h, t, 2, p, T, A, B, i0, Ns, H, 2, prec); arf_set(res, arb_midref(h+1)); _arb_vec_clear(t, 2); _arb_vec_clear(h, 2); } /* Accounts for limited resolution and supporting points. */ static void _interpolation_helper(arb_t res, const acb_dirichlet_platt_ws_precomp_t pre, const arb_t t0, arb_srcptr p, const fmpz_t T, slong A, slong B, slong i0, slong Ns, const arb_t H, slong sigma, slong prec) { arb_t total, err; arb_init(total); arb_init(err); _interpolation_helper_raw( total, t0, p, T, A, B, i0, Ns, H, prec); acb_dirichlet_platt_bound_C3(err, t0, A, H, Ns, prec); arb_add_error(total, err); acb_dirichlet_platt_i_bound_precomp( err, &pre->pre_i, &pre->pre_c, t0, A, H, sigma, prec); arb_add_error(total, err); arb_set(res, total); arb_clear(total); arb_clear(err); } void acb_dirichlet_platt_ws_precomp_init(acb_dirichlet_platt_ws_precomp_t pre, slong A, const arb_t H, slong sigma, slong prec) { acb_dirichlet_platt_c_precomp_init(&pre->pre_c, sigma, H, 0, prec); acb_dirichlet_platt_i_precomp_init(&pre->pre_i, A, H, sigma, prec); } void acb_dirichlet_platt_ws_precomp_clear(acb_dirichlet_platt_ws_precomp_t pre) { acb_dirichlet_platt_c_precomp_clear(&pre->pre_c); acb_dirichlet_platt_i_precomp_clear(&pre->pre_i); } void acb_dirichlet_platt_ws_interpolation_precomp(arb_t res, arf_t deriv, const acb_dirichlet_platt_ws_precomp_t pre, const arb_t t0, arb_srcptr p, const fmpz_t T, slong A, slong B, slong Ns_max, const arb_t H, slong sigma, slong prec) { slong N = A*B; if (A < 1 || B < 1 || N % 2) { flint_printf("requires an even number of grid points\n"); flint_abort(); } else { arb_t x, dt0, dt0A, total; arf_t lower_f; slong n, lower_n; arb_init(x); arb_init(dt0); arb_init(dt0A); arb_init(total); arf_init(lower_f); arb_sub_fmpz(dt0, t0, T, prec + fmpz_clog_ui(T, 2)); arb_mul_si(dt0A, dt0, A, prec); arb_get_lbound_arf(lower_f, dt0A, prec); lower_n = arf_get_si(lower_f, ARF_RND_FLOOR); if (deriv) { arf_zero(deriv); } /* * More than one iteration is needed only when the set of * supporting points for interpolation is uncertain. */ for (n = lower_n; n == lower_n || arb_contains_si(dt0A, n); n++) { slong nlow = N/2 + n + 1; slong nhigh = N/2 - n - 1; slong Ns = FLINT_MIN(Ns_max, FLINT_MIN(nlow, nhigh)); if (Ns < 1) { arb_zero_pm_inf(total); } else { slong i0 = N/2 + n - (Ns - 1); if (res) { _interpolation_helper( x, pre, t0, p, T, A, B, i0, Ns, H, sigma, prec); if (n == lower_n) { arb_set(total, x); } else { arb_union(total, total, x, prec); } } if (deriv) { _interpolation_deriv_helper( deriv, t0, p, T, A, B, i0, Ns, H, prec); } } } if (res) { arb_set(res, total); } arb_clear(x); arb_clear(dt0); arb_clear(dt0A); arb_clear(total); arf_clear(lower_f); } } void acb_dirichlet_platt_ws_interpolation(arb_t res, arf_t deriv, const arb_t t0, arb_srcptr p, const fmpz_t T, slong A, slong B, slong Ns_max, const arb_t H, slong sigma, slong prec) { acb_dirichlet_platt_ws_precomp_t pre; acb_dirichlet_platt_ws_precomp_init(pre, A, H, sigma, prec); acb_dirichlet_platt_ws_interpolation_precomp( res, deriv, pre, t0, p, T, A, B, Ns_max, H, sigma, prec); acb_dirichlet_platt_ws_precomp_clear(pre); }