diff --git a/examples/integrals.c b/examples/integrals.c index b6458a63ca..d93d3ae580 100644 --- a/examples/integrals.c +++ b/examples/integrals.c @@ -732,11 +732,22 @@ scaled_bessel_select_N(arb_t N, ulong k, slong prec) arb_mul_2exp_si(N, N, e); } +/* f(z) = Si(z) */ +int +f_si(acb_ptr res, const acb_t z, void * param, slong order, slong prec) +{ + if (order > 1) + flint_abort(); /* Would be needed for Taylor method. */ + + acb_hypgeom_si(res, z, prec); + return 0; +} + /* ------------------------------------------------------------------------- */ /* Main test program */ /* ------------------------------------------------------------------------- */ -#define NUM_INTEGRALS 37 +#define NUM_INTEGRALS 38 const char * descr[NUM_INTEGRALS] = { @@ -777,6 +788,7 @@ const char * descr[NUM_INTEGRALS] = "int_0^{inf} exp(-x) I_0(x/15)^{15} dx (using domain truncation)", "int_{-1-i}^{-1+i} 1/sqrt(x) dx", "int_0^{inf} 1/gamma(x) dx (using domain truncation)", + "int_0^{1000} Si(x) dx", }; int main(int argc, char *argv[]) @@ -1273,6 +1285,13 @@ int main(int argc, char *argv[]) arb_add_error_2exp_si(acb_realref(s), -goal); break; + case 37: + acb_zero(a); + acb_set_ui(b, 1000); + acb_calc_integrate(s, f_si, NULL, a, b, goal, tol, options, prec); + break; + + default: abort(); } diff --git a/src/acb_hypgeom/chi.c b/src/acb_hypgeom/chi.c index cdec409c6a..1a1b86f852 100644 --- a/src/acb_hypgeom/chi.c +++ b/src/acb_hypgeom/chi.c @@ -9,6 +9,7 @@ (at your option) any later version. See . */ +#include #include "acb.h" #include "acb_hypgeom.h" @@ -101,51 +102,119 @@ acb_hypgeom_chi_asymp(acb_t res, const acb_t z, slong prec) acb_clear(one); } +/* defined in ci.c */ void -acb_hypgeom_chi_2f3(acb_t res, const acb_t z, slong prec) -{ - acb_t a, t, u; - acb_struct b[3]; - - acb_init(a); - acb_init(b); - acb_init(b + 1); - acb_init(b + 2); - acb_init(t); - acb_init(u); - - acb_one(a); - acb_set_ui(b, 2); - acb_set(b + 1, b); - acb_set_ui(b + 2, 3); - acb_mul_2exp_si(b + 2, b + 2, -1); - - acb_mul(t, z, z, prec); - acb_mul_2exp_si(t, t, -2); - acb_hypgeom_pfq_direct(u, a, 1, b, 3, t, -1, prec); - acb_mul(u, u, t, prec); +acb_hypgeom_chi_2f3(acb_t res, const acb_t z, slong prec); - acb_log(t, z, prec); - acb_add(u, u, t, prec); +/* Bound propagated error |chi(z)-chi(mid(z))| assuming that we + are off the branch cut (not checked here). + Uses |chi'(z)| = |cosh(z)/z| <= cosh(|re(z)|)/|z|. +*/ +static void +acb_hypgeom_chi_prop_error(mag_t err, const acb_t z) +{ + if (acb_is_exact(z)) + { + mag_zero(err); + } + else + { + mag_t t, u; - arb_const_euler(acb_realref(t), prec); - arb_add(acb_realref(u), acb_realref(u), acb_realref(t), prec); + mag_init(t); + mag_init(u); - acb_swap(res, u); + arb_get_mag(t, acb_realref(z)); + mag_cosh(t, t); + acb_get_mag_lower(u, z); + mag_div(t, t, u); + mag_hypot(u, arb_radref(acb_realref(z)), arb_radref(acb_imagref(z))); + mag_mul(err, t, u); - acb_clear(a); - acb_clear(b); - acb_clear(b + 1); - acb_clear(b + 2); - acb_clear(t); - acb_clear(u); + mag_clear(t); + mag_clear(u); + } } void acb_hypgeom_chi(acb_t res, const acb_t z, slong prec) { - if (acb_hypgeom_u_use_asymp(z, prec)) - acb_hypgeom_chi_asymp(res, z, prec); + if (!acb_is_finite(z) || acb_contains_zero(z)) + { + acb_indeterminate(res); + } +/* todo -- good real code + else if (acb_is_real(z)) + { + } +*/ + else if (arb_contains_zero(acb_imagref(z)) && + !arb_is_nonnegative(acb_imagref(z)) && + !arb_is_positive(acb_realref(z))) + { + /* We straddle the branch cut; do something generic */ + if (acb_hypgeom_u_use_asymp(z, prec)) + acb_hypgeom_chi_asymp(res, z, prec); + else + acb_hypgeom_chi_2f3(res, z, prec); + } else - acb_hypgeom_chi_2f3(res, z, prec); + { + double asymp_accuracy, a, b, absz, cancellation, rlog2 = 1.4426950408889634; + slong wp; + int use_asymp; + acb_t m; + mag_t err; + /* since we don't have a special case for real z */ + int pure_real = arb_is_zero(acb_imagref(z)); + + a = arf_get_d(arb_midref(acb_realref(z)), ARF_RND_DOWN); + b = arf_get_d(arb_midref(acb_imagref(z)), ARF_RND_DOWN); + a = fabs(a); + b = fabs(b); + absz = sqrt(a * a + b * b); + + if (a <= 1.0 && b <= 1.0) + { + use_asymp = 0; + } + else if (a > prec || b > prec) + { + use_asymp = 1; + } + else + { + asymp_accuracy = absz * rlog2 * 0.999 - 5; + use_asymp = asymp_accuracy > prec; + } + + acb_init(m); + mag_init(err); + + /* assumes that we already handled the branch cut */ + acb_hypgeom_chi_prop_error(err, z); + acb_get_mid(m, z); + + if (use_asymp) + { + acb_hypgeom_chi_asymp(res, m, prec); + } + else + { + /* terms grow to ~ exp(|z|), sum is ~ exp(|re(z)|) */ + cancellation = (absz - a) * rlog2; + wp = prec + FLINT_MAX(0, cancellation); + wp = wp * 1.001 + 5; + acb_hypgeom_chi_2f3(res, m, wp); + acb_set_round(res, res, prec); + } + + if (pure_real) + arb_add_error_mag(acb_realref(res), err); + else + acb_add_error_mag(res, err); + + acb_clear(m); + mag_clear(err); + } } diff --git a/src/acb_hypgeom/ci.c b/src/acb_hypgeom/ci.c index 7c9ce2351b..2aeac193e2 100644 --- a/src/acb_hypgeom/ci.c +++ b/src/acb_hypgeom/ci.c @@ -1,5 +1,5 @@ /* - Copyright (C) 2015 Fredrik Johansson + Copyright (C) 2015, 2024 Fredrik Johansson This file is part of FLINT. @@ -9,6 +9,7 @@ (at your option) any later version. See . */ +#include #include "acb.h" #include "arb_hypgeom.h" #include "acb_hypgeom.h" @@ -109,8 +110,8 @@ acb_hypgeom_ci_asymp(acb_t res, const acb_t z, slong prec) acb_clear(one); } -void -acb_hypgeom_ci_2f3(acb_t res, const acb_t z, slong prec) +static void +_acb_hypgeom_ci_2f3(acb_t res, const acb_t z, int hyperbolic, slong prec) { acb_t a, t, u; acb_struct b[3]; @@ -130,7 +131,8 @@ acb_hypgeom_ci_2f3(acb_t res, const acb_t z, slong prec) acb_mul(t, z, z, prec); acb_mul_2exp_si(t, t, -2); - acb_neg(t, t); + if (!hyperbolic) + acb_neg(t, t); acb_hypgeom_pfq_direct(u, a, 1, b, 3, t, -1, prec); acb_mul(u, u, t, prec); @@ -150,31 +152,131 @@ acb_hypgeom_ci_2f3(acb_t res, const acb_t z, slong prec) acb_clear(u); } +void +acb_hypgeom_ci_2f3(acb_t res, const acb_t z, slong prec) +{ + _acb_hypgeom_ci_2f3(res, z, 0, prec); +} + +void +acb_hypgeom_chi_2f3(acb_t res, const acb_t z, slong prec) +{ + _acb_hypgeom_ci_2f3(res, z, 1, prec); +} + +/* Bound propagated error |ci(z)-ci(mid(z))| assuming that we + are off the branch cut (not checked here). + Uses |ci'(z)| = |cos(z)/z| <= cosh(|im(z)|)/|z|. +*/ +static void +acb_hypgeom_ci_prop_error(mag_t err, const acb_t z) +{ + if (acb_is_exact(z)) + { + mag_zero(err); + } + else + { + mag_t t, u; + + mag_init(t); + mag_init(u); + + arb_get_mag(t, acb_imagref(z)); + mag_cosh(t, t); + acb_get_mag_lower(u, z); + mag_div(t, t, u); + mag_hypot(u, arb_radref(acb_realref(z)), arb_radref(acb_imagref(z))); + mag_mul(err, t, u); + + mag_clear(t); + mag_clear(u); + } +} + void acb_hypgeom_ci(acb_t res, const acb_t z, slong prec) { - if (acb_is_real(z) && arb_is_finite(acb_realref(z))) + if (!acb_is_finite(z) || acb_contains_zero(z)) + { + acb_indeterminate(res); + } + else if (acb_is_real(z)) { if (arb_is_positive(acb_realref(z))) { arb_hypgeom_ci(acb_realref(res), acb_realref(z), prec); arb_zero(acb_imagref(res)); } - else if (arb_is_negative(acb_realref(z))) + else /* arb_is_negative(acb_realref(z)), since we already tested for 0 */ { arb_neg(acb_realref(res), acb_realref(z)); arb_hypgeom_ci(acb_realref(res), acb_realref(res), prec); arb_const_pi(acb_imagref(res), prec); } + } + else if (arb_contains_zero(acb_imagref(z)) && + !arb_is_nonnegative(acb_imagref(z)) && + !arb_is_positive(acb_realref(z))) + { + /* We straddle the branch cut; do something generic */ + if (acb_hypgeom_u_use_asymp(z, prec)) + acb_hypgeom_ci_asymp(res, z, prec); else + acb_hypgeom_ci_2f3(res, z, prec); + } + else + { + double asymp_accuracy, a, b, absz, cancellation, rlog2 = 1.4426950408889634; + slong wp; + int use_asymp; + acb_t m; + mag_t err; + + a = arf_get_d(arb_midref(acb_realref(z)), ARF_RND_DOWN); + b = arf_get_d(arb_midref(acb_imagref(z)), ARF_RND_DOWN); + a = fabs(a); + b = fabs(b); + absz = sqrt(a * a + b * b); + + if (a <= 1.0 && b <= 1.0) { - acb_indeterminate(res); + use_asymp = 0; + } + else if (a > prec || b > prec) + { + use_asymp = 1; + } + else + { + asymp_accuracy = absz * rlog2 * 0.999 - 5; + use_asymp = asymp_accuracy > prec; } - return; - } - if (acb_hypgeom_u_use_asymp(z, prec)) - acb_hypgeom_ci_asymp(res, z, prec); - else - acb_hypgeom_ci_2f3(res, z, prec); + acb_init(m); + mag_init(err); + + /* assumes that we already handled the branch cut */ + acb_hypgeom_ci_prop_error(err, z); + acb_get_mid(m, z); + + if (use_asymp) + { + acb_hypgeom_ci_asymp(res, m, prec); + } + else + { + /* terms grow to ~ exp(|z|), sum is ~ exp(|im(z)|) */ + cancellation = (absz - b) * rlog2; + wp = prec + FLINT_MAX(0, cancellation); + wp = wp * 1.001 + 5; + acb_hypgeom_ci_2f3(res, m, wp); + acb_set_round(res, res, prec); + } + + acb_add_error_mag(res, err); + + acb_clear(m); + mag_clear(err); + } } diff --git a/src/acb_hypgeom/ei.c b/src/acb_hypgeom/ei.c index e81d7f4ee7..fc1abb8416 100644 --- a/src/acb_hypgeom/ei.c +++ b/src/acb_hypgeom/ei.c @@ -9,6 +9,7 @@ (at your option) any later version. See . */ +#include #include "acb.h" #include "acb_hypgeom.h" @@ -120,11 +121,130 @@ acb_hypgeom_ei_2f2(acb_t res, const acb_t z, slong prec) acb_clear(t); } +/* Bound propagated error |ei(z)-ei(mid(z))| assuming that we + are off the branch cut (not checked here). + Uses |ei'(z)| = |exp(z)/z| <= exp(re(z))/|z|. +*/ +static void +acb_hypgeom_ei_prop_error(mag_t err, const acb_t z) +{ + if (acb_is_exact(z)) + { + mag_zero(err); + } + else + { + mag_t t, u; + arf_t x; + + mag_init(t); + mag_init(u); + arf_init(x); + + arb_get_ubound_arf(x, acb_realref(z), MAG_BITS); + + if (arf_sgn(x) >= 0) + { + arf_get_mag(t, x); + mag_exp(t, t); + } + else + { + arf_get_mag_lower(t, x); + mag_expinv(t, t); + } + + acb_get_mag_lower(u, z); + mag_div(t, t, u); + mag_hypot(u, arb_radref(acb_realref(z)), arb_radref(acb_imagref(z))); + mag_mul(err, t, u); + + mag_clear(t); + mag_clear(u); + arf_clear(x); + } +} + void acb_hypgeom_ei(acb_t res, const acb_t z, slong prec) { - if (acb_hypgeom_u_use_asymp(z, prec)) - acb_hypgeom_ei_asymp(res, z, prec); + if (!acb_is_finite(z) || acb_contains_zero(z)) + { + acb_indeterminate(res); + } +/* todo -- good real code + else if (acb_is_real(z)) + { + } +*/ + else if (arb_contains_zero(acb_imagref(z)) && + !arb_is_zero(acb_imagref(z)) && + !arb_is_positive(acb_realref(z))) + { + /* We straddle the branch cut; do something generic */ + if (acb_hypgeom_u_use_asymp(z, prec)) + acb_hypgeom_ei_asymp(res, z, prec); + else + acb_hypgeom_ei_2f2(res, z, prec); + } else - acb_hypgeom_ei_2f2(res, z, prec); + { + double asymp_accuracy, a, asigned, b, absz, cancellation, rlog2 = 1.4426950408889634; + slong wp; + int use_asymp; + acb_t m; + mag_t err; + /* since we don't have a special case for real z */ + int pure_real = arb_is_zero(acb_imagref(z)); + + a = arf_get_d(arb_midref(acb_realref(z)), ARF_RND_DOWN); + b = arf_get_d(arb_midref(acb_imagref(z)), ARF_RND_DOWN); + asigned = a; + a = fabs(a); + b = fabs(b); + absz = sqrt(a * a + b * b); + + if (a <= 1.0 && b <= 1.0) + { + use_asymp = 0; + } + else if (a > prec || b > prec) + { + use_asymp = 1; + } + else + { + asymp_accuracy = absz * rlog2 * 0.999 - 5; + use_asymp = asymp_accuracy > prec; + } + + acb_init(m); + mag_init(err); + + /* assumes that we already handled the branch cut */ + acb_hypgeom_ei_prop_error(err, z); + acb_get_mid(m, z); + + if (use_asymp) + { + acb_hypgeom_ei_asymp(res, m, prec); + } + else + { + /* terms grow to ~ exp(|z|), sum is ~ exp(re(z)) */ + cancellation = (absz - asigned) * rlog2; + wp = prec + FLINT_MAX(0, cancellation); + wp = wp * 1.001 + 5; + acb_hypgeom_ei_2f2(res, m, wp); + acb_set_round(res, res, prec); + } + + if (pure_real) + arb_add_error_mag(acb_realref(res), err); + else + acb_add_error_mag(res, err); + + acb_clear(m); + mag_clear(err); + } } diff --git a/src/acb_hypgeom/si.c b/src/acb_hypgeom/si.c index 704f9096b3..0f2db1bdfd 100644 --- a/src/acb_hypgeom/si.c +++ b/src/acb_hypgeom/si.c @@ -1,5 +1,5 @@ /* - Copyright (C) 2015 Fredrik Johansson + Copyright (C) 2015, 2024 Fredrik Johansson This file is part of FLINT. @@ -9,6 +9,7 @@ (at your option) any later version. See . */ +#include #include "acb.h" #include "arb_hypgeom.h" #include "acb_hypgeom.h" @@ -80,6 +81,9 @@ acb_hypgeom_si_asymp(acb_t res, const acb_t z, slong prec) acb_swap(res, t); + if (!acb_is_finite(res)) + acb_indeterminate(res); + acb_clear(t); acb_clear(u); acb_clear(w); @@ -121,18 +125,105 @@ acb_hypgeom_si_1f2(acb_t res, const acb_t z, slong prec) acb_clear(t); } +/* Bound propagated error |si(z)-si(mid(z))| using + |si'(z)| = |sin(z)/z| <= cosh(|im(z)|)/max(1, |z|). +*/ +static void +acb_hypgeom_si_prop_error(mag_t err, const acb_t z) +{ + if (acb_is_exact(z)) + { + mag_zero(err); + } + else + { + mag_t t, u; + + mag_init(t); + mag_init(u); + + arb_get_mag(t, acb_imagref(z)); + mag_cosh(t, t); + acb_get_mag_lower(u, z); + if (mag_cmp_2exp_si(u, 0) < 0) + mag_one(u); + mag_div(t, t, u); + mag_hypot(u, arb_radref(acb_realref(z)), arb_radref(acb_imagref(z))); + mag_mul(err, t, u); + + mag_clear(t); + mag_clear(u); + } +} + void acb_hypgeom_si(acb_t res, const acb_t z, slong prec) { - if (acb_is_real(z) && arb_is_finite(acb_realref(z))) + if (!acb_is_finite(z)) + { + acb_indeterminate(res); + } + else if (acb_is_real(z)) { arb_hypgeom_si(acb_realref(res), acb_realref(z), prec); arb_zero(acb_imagref(res)); - return; } - - if (acb_hypgeom_u_use_asymp(z, prec)) - acb_hypgeom_si_asymp(res, z, prec); else - acb_hypgeom_si_1f2(res, z, prec); + { + double asymp_accuracy, a, b, absz, cancellation, rlog2 = 1.4426950408889634; + slong wp; + int use_asymp; + acb_t m; + mag_t err; + int pure_imag = arb_is_zero(acb_realref(z)); + + a = arf_get_d(arb_midref(acb_realref(z)), ARF_RND_DOWN); + b = arf_get_d(arb_midref(acb_imagref(z)), ARF_RND_DOWN); + a = fabs(a); + b = fabs(b); + absz = sqrt(a * a + b * b); + + if (a <= 1.0 && b <= 1.0) + { + use_asymp = 0; + } + else if (a > prec || b > prec) + { + use_asymp = 1; + } + else + { + asymp_accuracy = absz * rlog2 * 0.999 - 5; + use_asymp = asymp_accuracy > prec; + } + + acb_init(m); + mag_init(err); + + /* assumes that we already handled the branch cut */ + acb_hypgeom_si_prop_error(err, z); + acb_get_mid(m, z); + + if (use_asymp) + { + acb_hypgeom_si_asymp(res, m, prec); + } + else + { + /* terms grow to ~ exp(|z|), sum is ~ exp(|im(z)|) */ + cancellation = (absz - b) * rlog2; + wp = prec + FLINT_MAX(0, cancellation); + wp = wp * 1.001 + 5; + acb_hypgeom_si_1f2(res, m, wp); + acb_set_round(res, res, prec); + } + + if (pure_imag) + arb_add_error_mag(acb_imagref(res), err); + else + acb_add_error_mag(res, err); + + acb_clear(m); + mag_clear(err); + } } diff --git a/src/python/flint_ctypes.py b/src/python/flint_ctypes.py index f12975d88c..2cc0ac1698 100644 --- a/src/python/flint_ctypes.py +++ b/src/python/flint_ctypes.py @@ -1913,7 +1913,7 @@ def exp_integral(ctx, x, y): def exp_integral_ei(ctx, x): """ >>> RR.exp_integral_ei(1) - [1.89511781635594 +/- 5.11e-15] + [1.895117816355937 +/- 6.91e-16] """ return ctx._unary_op(x, libgr.gr_exp_integral_ei, "exp_integral_ei($x)") @@ -1921,6 +1921,10 @@ def sin_integral(ctx, x): """ >>> RR.sin_integral(1) [0.946083070367183 +/- 1.35e-16] + >>> CC.sin_integral(CC("(5 +/- 1e-10)*I")) + [20.09321183 +/- 5.79e-9]*I + >>> CC.sin_integral(CC("10 + (1 +/- 1e-10)*I")) + ([1.7002629761 +/- 6.33e-11] + [-0.0667638998 +/- 2.17e-11]*I) """ return ctx._unary_op(x, libgr.gr_sin_integral, "sin_integral($x)") @@ -1934,21 +1938,21 @@ def cos_integral(ctx, x): def sinh_integral(ctx, x): """ >>> RR.sinh_integral(1) - [1.05725087537573 +/- 2.77e-15] + [1.057250875375728 +/- 6.29e-16] """ return ctx._unary_op(x, libgr.gr_sinh_integral, "sinh_integral($x)") def cosh_integral(ctx, x): """ >>> RR.cosh_integral(1) - [0.837866940980208 +/- 4.78e-16] + [0.837866940980208 +/- 3.15e-16] """ return ctx._unary_op(x, libgr.gr_cosh_integral, "cosh_integral($x)") def log_integral(ctx, x, offset=False): """ >>> RR.log_integral(2) - [1.04516378011749 +/- 4.01e-15] + [1.04516378011749 +/- 3.31e-15] >>> RR.log_integral(2, offset=True) 0 """