attempt to improve bounds for Airy functions

This commit is contained in:
fredrik 2018-02-11 22:28:49 +01:00
parent a7d9c0b1c1
commit d64055fb3f
3 changed files with 362 additions and 143 deletions

View file

@ -102,8 +102,8 @@ estimate_airy(double x, double y, int ai)
/* error propagation based on derivatives */ /* error propagation based on derivatives */
void void
acb_hypgeom_airy_direct_prop(acb_t ai, acb_t aip, acb_t bi, acb_t bip, acb_hypgeom_airy_prop(acb_t ai, acb_t aip, acb_t bi, acb_t bip,
const acb_t z, slong n, slong prec) const acb_t z, slong n, int algo, slong prec)
{ {
mag_t aib, aipb, bib, bipb, zb, rad; mag_t aib, aipb, bib, bipb, zb, rad;
acb_t zz; acb_t zz;
@ -124,7 +124,10 @@ acb_hypgeom_airy_direct_prop(acb_t ai, acb_t aip, acb_t bi, acb_t bip,
acb_get_mag(zb, z); acb_get_mag(zb, z);
acb_hypgeom_airy_bound(aib, aipb, bib, bipb, z); acb_hypgeom_airy_bound(aib, aipb, bib, bipb, z);
acb_hypgeom_airy_direct(ai, aip, bi, bip, zz, n, prec); if (algo == 0)
acb_hypgeom_airy_direct(ai, aip, bi, bip, zz, n, prec);
else
acb_hypgeom_airy_asymp(ai, aip, bi, bip, zz, n, prec);
if (ai != NULL) if (ai != NULL)
{ {
@ -173,6 +176,24 @@ acb_hypgeom_airy_direct_prop(acb_t ai, acb_t aip, acb_t bi, acb_t bip,
acb_clear(zz); acb_clear(zz);
} }
void
acb_hypgeom_airy_direct_prop(acb_t ai, acb_t aip, acb_t bi, acb_t bip,
const acb_t z, slong n, slong prec)
{
acb_hypgeom_airy_prop(ai, aip, bi, bip, z, n, 0, prec);
}
void
acb_hypgeom_airy_asymp2(acb_t ai, acb_t aip, acb_t bi, acb_t bip,
const acb_t z, slong n, slong prec)
{
/* avoid singularity in asymptotic expansion near 0 */
if (acb_rel_accuracy_bits(z) > 3)
acb_hypgeom_airy_asymp(ai, aip, bi, bip, z, n, prec);
else
acb_hypgeom_airy_prop(ai, aip, bi, bip, z, n, 1, prec);
}
void void
acb_hypgeom_airy(acb_t ai, acb_t aip, acb_t bi, acb_t bip, const acb_t z, slong prec) acb_hypgeom_airy(acb_t ai, acb_t aip, acb_t bi, acb_t bip, const acb_t z, slong prec)
{ {
@ -210,7 +231,10 @@ acb_hypgeom_airy(acb_t ai, acb_t aip, acb_t bi, acb_t bip, const acb_t z, slong
n = wp / (-zmag) + 1; n = wp / (-zmag) + 1;
} }
acb_hypgeom_airy_direct(ai, aip, bi, bip, z, n, wp); if (acb_is_exact(z))
acb_hypgeom_airy_direct(ai, aip, bi, bip, z, n, wp);
else
acb_hypgeom_airy_direct_prop(ai, aip, bi, bip, z, n, wp);
} /* huge input -- use asymptotics and pick n without overflowing */ } /* huge input -- use asymptotics and pick n without overflowing */
else if ((arf_cmpabs_2exp_si(re, 64) > 0 || arf_cmpabs_2exp_si(im, 64) > 0)) else if ((arf_cmpabs_2exp_si(re, 64) > 0 || arf_cmpabs_2exp_si(im, 64) > 0))
{ {
@ -227,7 +251,7 @@ acb_hypgeom_airy(acb_t ai, acb_t aip, acb_t bi, acb_t bip, const acb_t z, slong
n = FLINT_MAX(n, 1); n = FLINT_MAX(n, 1);
} }
acb_hypgeom_airy_asymp(ai, aip, bi, bip, z, n, wp); acb_hypgeom_airy_asymp2(ai, aip, bi, bip, z, n, wp);
} }
else /* moderate input */ else /* moderate input */
{ {
@ -239,14 +263,18 @@ acb_hypgeom_airy(acb_t ai, acb_t aip, acb_t bi, acb_t bip, const acb_t z, slong
if (zmag >= 4.0 && (n = asymp_pick_terms(wp, log(zmag))) != -1) if (zmag >= 4.0 && (n = asymp_pick_terms(wp, log(zmag))) != -1)
{ {
acb_hypgeom_airy_asymp(ai, aip, bi, bip, z, n, wp); acb_hypgeom_airy_asymp2(ai, aip, bi, bip, z, n, wp);
} }
else if (zmag <= 1.5) else if (zmag <= 1.5)
{ {
t = 3 * (wp * LOG2) / (2 * z15 * EXP1); t = 3 * (wp * LOG2) / (2 * z15 * EXP1);
t = (wp * LOG2) / (2 * d_lambertw(t)); t = (wp * LOG2) / (2 * d_lambertw(t));
n = FLINT_MAX(t + 1, 2); n = FLINT_MAX(t + 1, 2);
acb_hypgeom_airy_direct(ai, aip, bi, bip, z, n, wp);
if (acb_is_exact(z))
acb_hypgeom_airy_direct(ai, aip, bi, bip, z, n, wp);
else
acb_hypgeom_airy_direct_prop(ai, aip, bi, bip, z, n, wp);
} }
else else
{ {

View file

@ -24,65 +24,29 @@ arb_bound_exp_neg(mag_t b, const arb_t x)
arb_clear(t); arb_clear(t);
} }
/* todo -- should be lt in asymp code? */ /* Implements DLMF 9.7.17. We assume |zeta| >= 1/2 and |arg(z)| <= 2 pi/3 here,
static int ignoring the smaller points which must be dealt with separately. */
arg_le_2pi3(const acb_t z, const acb_t zeta)
{
if (arb_is_nonnegative(acb_realref(z)))
return 1;
if (arb_is_positive(acb_imagref(z)) &&
arb_is_nonnegative(acb_imagref(zeta)))
return 1;
if (arb_is_negative(acb_imagref(z)) &&
arb_is_nonpositive(acb_imagref(zeta)))
return 1;
return 0;
}
void void
acb_hypgeom_airy_bound_9_7_17(mag_t bound, const acb_t z, const acb_t zeta) acb_hypgeom_airy_bound_9_7_17(mag_t bound, const acb_t z, const acb_t zeta)
{ {
mag_t D, t, u, v, zeta_lower; mag_t D, t, u, v, zeta_lower, half;
mag_init(D); mag_init(D);
mag_init(t); mag_init(t);
mag_init(u); mag_init(u);
mag_init(v); mag_init(v);
mag_init(zeta_lower); mag_init(zeta_lower);
mag_init(half);
mag_one(half);
mag_mul_2exp_si(half, half, -1);
acb_get_mag_lower(zeta_lower, zeta); acb_get_mag_lower(zeta_lower, zeta);
mag_max(zeta_lower, zeta_lower, half); /* by assumption */
/* 2 chi(1) exp(7 pi / (72 |zeta|)) * c_1 */ /* 2 chi(1) exp(7 pi / (72 |zeta|)) * c_1 */
/* simplified bound */ /* simplified bound assuming |zeta| >= 1/2 */
if (mag_cmp_2exp_si(zeta_lower, -1) >= 0) mag_one(D);
mag_one(D);
else
mag_inf(D);
if (!arg_le_2pi3(z, zeta))
{
arb_get_mag_lower(u, acb_realref(zeta));
arb_get_mag(v, acb_imagref(zeta));
/* exp(7 pi / (36 u)) < exp(5/(8 u)) */
mag_set_ui_2exp_si(t, 5, -3);
mag_div(t, t, u);
mag_exp(t, t);
/* |1/cos(arg(zeta))| = sqrt(1+(v/u)^2) */
mag_div(v, v, u);
mag_mul(v, v, v);
mag_one(u);
mag_add(v, v, u);
mag_sqrt(v, v);
mag_mul(t, t, v);
/* c_1 * 4 chi(1) < 0.62 < 1 -- do nothing */
mag_max(D, D, t);
}
/* exp(-zeta) / (2 sqrt(pi)) * (1 + D / |zeta|) */ /* exp(-zeta) / (2 sqrt(pi)) * (1 + D / |zeta|) */
@ -105,21 +69,238 @@ acb_hypgeom_airy_bound_9_7_17(mag_t bound, const acb_t z, const acb_t zeta)
mag_clear(zeta_lower); mag_clear(zeta_lower);
} }
void
acb_hypgeom_airy_bound_arg_le_2pi3(mag_t A, mag_t B, const acb_t z, slong wp)
{
acb_t zeta, z1;
acb_init(zeta);
acb_init(z1);
acb_set_round(zeta, z, wp);
acb_sqrt(zeta, zeta, wp);
acb_cube(zeta, zeta, wp);
acb_mul_2exp_si(zeta, zeta, 1);
acb_div_ui(zeta, zeta, 3, wp);
acb_hypgeom_airy_bound_9_7_17(A, z, zeta);
/* Use Bi(z) = w_1 Ai(z) + 2 w_2 Ai(z exp(+/- 2pi i / 3)),
where w_1, w_2 are roots of unity */
if (B != NULL)
{
arb_sqrt_ui(acb_imagref(z1), 3, wp);
arb_set_si(acb_realref(z1), -1);
acb_mul_2exp_si(z1, z1, -1);
/* multiply by exp(-2 pi i / 3) in upper half plane
and by exp(2 pi i / 3) in lower half plane, to stay close
to positive reals */
/* in principle, we should be computing the union of both cases,
but since Bi is conjugate symmetric with the maximum value
attained on the positive real line, it's sufficient to consider
the case centered on the midpoint */
if (arf_sgn(arb_midref(acb_imagref(z))) >= 0)
acb_conj(z1, z1);
acb_mul(z1, z1, z, wp);
acb_neg(zeta, zeta); /* same effect regardless of exp(+/-2 pi i/3) */
acb_hypgeom_airy_bound_9_7_17(B, z1, zeta);
mag_mul_2exp_si(B, B, 1);
mag_add(B, B, A);
}
acb_clear(zeta);
acb_clear(z1);
}
/* near the negative half line, use
|Ai(-z)|, |Bi(-z)| <= |Ai(z exp(pi i/3))| + |Ai(z exp(-pi i/3))| */
void
acb_hypgeom_airy_bound_arg_ge_2pi3(mag_t A, mag_t B, const acb_t z, slong wp)
{
acb_t zeta, z1, z2;
acb_init(zeta);
acb_init(z1);
acb_init(z2);
arb_sqrt_ui(acb_imagref(z1), 3, wp);
arb_one(acb_realref(z1));
acb_mul_2exp_si(z1, z1, -1);
acb_conj(z2, z1);
acb_neg_round(zeta, z, wp);
acb_mul(z1, z1, zeta, wp);
acb_sqrt(zeta, zeta, wp);
acb_cube(zeta, zeta, wp);
acb_mul_2exp_si(zeta, zeta, 1);
acb_div_ui(zeta, zeta, 3, wp);
acb_mul_onei(zeta, zeta);
acb_hypgeom_airy_bound_9_7_17(A, z1, zeta);
/* conjugate symmetry */
if (arb_is_zero(acb_imagref(z)))
{
mag_mul_2exp_si(A, A, 1);
}
else
{
mag_t D;
mag_init(D);
acb_mul(z2, z2, zeta, wp);
acb_neg(zeta, zeta);
acb_hypgeom_airy_bound_9_7_17(D, z2, zeta);
mag_add(A, A, D);
mag_clear(D);
}
if (B != NULL)
mag_set(B, A);
acb_clear(zeta);
acb_clear(z1);
acb_clear(z2);
}
static int
arg_lt_2pi3_fast(const acb_t z)
{
arf_t t;
mag_t x, y, s;
int res;
if (arb_is_zero(acb_imagref(z)) && arb_is_nonnegative(acb_realref(z)))
return 1;
arf_init(t);
mag_init(x);
mag_init(y);
mag_init(s);
arf_set_mag(t, arb_radref(acb_realref(z)));
arf_sub(t, arb_midref(acb_realref(z)), t, MAG_BITS, ARF_RND_FLOOR);
if (arf_sgn(t) >= 0)
{
res = 1;
}
else
{
arf_get_mag(x, t);
arb_get_mag_lower(y, acb_imagref(z));
mag_set_ui(s, 3);
mag_sqrt(s, s);
mag_mul(s, s, x);
res = mag_cmp(s, y) <= 0;
}
arf_clear(t);
mag_clear(x);
mag_clear(y);
mag_clear(s);
return res;
}
static int
arg_gt_2pi3_fast(const acb_t z)
{
arf_t t;
mag_t x, y, s;
int res;
if (arb_is_zero(acb_imagref(z)) && arb_is_negative(acb_realref(z)))
return 1;
arf_init(t);
mag_init(x);
mag_init(y);
mag_init(s);
arf_set_mag(t, arb_radref(acb_realref(z)));
arf_add(t, arb_midref(acb_realref(z)), t, MAG_BITS, ARF_RND_CEIL);
if (arf_sgn(t) >= 0)
{
res = 0;
}
else
{
arf_get_mag_lower(x, t);
arb_get_mag(y, acb_imagref(z));
mag_set_ui_lower(s, 3);
mag_sqrt_lower(s, s);
mag_mul_lower(s, s, x);
res = mag_cmp(s, y) >= 0;
}
arf_clear(t);
mag_clear(x);
mag_clear(y);
mag_clear(s);
return res;
}
void void
acb_hypgeom_airy_bound(mag_t ai, mag_t aip, mag_t bi, mag_t bip, const acb_t z) acb_hypgeom_airy_bound(mag_t ai, mag_t aip, mag_t bi, mag_t bip, const acb_t z)
{ {
acb_t zeta; acb_t zeta;
acb_t z1, z2;
arf_srcptr zre, zim;
mag_t A, B, D, zlo, zhi; mag_t A, B, D, zlo, zhi;
slong wp; slong wp;
int near_zero;
if (acb_contains_zero(z)) /* Fast and slightly tighter bounds for real z <= 0 */
/* Todo: could implement bounds for z >= 0 too */
if (acb_is_real(z) && arb_is_nonpositive(acb_realref(z)))
{ {
if (ai != NULL) mag_inf(ai); mag_init(zlo);
if (aip != NULL) mag_inf(aip); mag_init(zhi);
if (bi != NULL) mag_inf(bi); mag_init(A);
if (bip != NULL) mag_inf(bip); mag_init(B);
mag_init(D);
if (ai != NULL || bi != NULL)
{
acb_get_mag_lower(zlo, z);
mag_rsqrt(A, zlo);
mag_sqrt(A, A);
mag_mul_ui(A, A, 150);
mag_set_ui(D, 160);
mag_min(A, A, D);
mag_mul_2exp_si(A, A, -8);
if (ai != NULL) mag_set(ai, A);
if (bi != NULL) mag_set(bi, A);
}
if (aip != NULL || bip != NULL)
{
acb_get_mag(zhi, z);
mag_sqrt(A, zhi);
mag_sqrt(A, A);
mag_mul_ui(A, A, 150);
mag_set_ui(D, 160);
mag_max(B, A, D);
mag_mul_2exp_si(B, B, -8);
mag_set_ui(D, 67);
mag_max(A, A, D);
mag_mul_2exp_si(A, A, -8);
if (aip != NULL) mag_set(aip, A);
if (bip != NULL) mag_set(bip, B);
}
mag_clear(zlo);
mag_clear(zhi);
mag_clear(A);
mag_clear(B);
mag_clear(D);
return; return;
} }
@ -131,103 +312,92 @@ acb_hypgeom_airy_bound(mag_t ai, mag_t aip, mag_t bi, mag_t bip, const acb_t z)
mag_init(zhi); mag_init(zhi);
wp = MAG_BITS * 2; wp = MAG_BITS * 2;
zre = arb_midref(acb_realref(z));
zim = arb_midref(acb_imagref(z));
/* near the negative half line, use acb_get_mag_lower(zlo, z);
|Ai(-z)|, |Bi(-z)| <= |Ai(z exp(pi i/3))| + |Ai(z exp(-pi i/3))| */ acb_get_mag(zhi, z);
if (arf_sgn(zre) < 0 && arf_cmpabs(zre, zim) > 0)
if (mag_cmp_2exp_si(zhi, 0) <= 0)
{ {
acb_init(z1); /* inside unit circle -- don't look at asymptotics */
acb_init(z2); if (ai != NULL) mag_set_ui_2exp_si(ai, 159, -8);
if (aip != NULL) mag_set_ui_2exp_si(aip, 125, -8);
arb_sqrt_ui(acb_imagref(z1), 3, wp); if (bi != NULL) mag_set_ui_2exp_si(bi, 310, -8);
arb_one(acb_realref(z1)); if (bip != NULL) mag_set_ui_2exp_si(bip, 239, -8);
acb_mul_2exp_si(z1, z1, -1);
acb_conj(z2, z1);
acb_neg_round(zeta, z, wp);
acb_mul(z1, z1, zeta, wp);
acb_sqrt(zeta, zeta, wp);
acb_cube(zeta, zeta, wp);
acb_mul_2exp_si(zeta, zeta, 1);
acb_div_ui(zeta, zeta, 3, wp);
acb_mul_onei(zeta, zeta);
acb_hypgeom_airy_bound_9_7_17(A, z1, zeta);
/* conjugate symmetry */
if (arb_is_zero(acb_imagref(z)))
{
mag_mul_2exp_si(A, A, 1);
}
else
{
acb_mul(z2, z2, zeta, wp);
acb_neg(zeta, zeta);
acb_hypgeom_airy_bound_9_7_17(D, z2, zeta);
mag_add(A, A, D);
}
mag_set(B, A);
acb_clear(z1);
acb_clear(z2);
} }
else else
{ {
acb_set_round(zeta, z, wp); /* look at asymptotics outside unit circle */
acb_sqrt(zeta, zeta, wp); near_zero = mag_cmp_2exp_si(zlo, 0) <= 0;
acb_cube(zeta, zeta, wp); if (near_zero)
acb_mul_2exp_si(zeta, zeta, 1); mag_one(zlo);
acb_div_ui(zeta, zeta, 3, wp);
acb_hypgeom_airy_bound_9_7_17(A, z, zeta); if (arg_lt_2pi3_fast(z))
/* Use Bi(z) = w_1 Ai(z) + 2 w_2 Ai(z exp(+/- 2pi i / 3)),
where w_1, w_2 are roots of unity */
if (bi != NULL || bip != NULL)
{ {
acb_init(z1); acb_hypgeom_airy_bound_arg_le_2pi3(A, (bi != NULL || bip != NULL) ? B : NULL, z, wp);
}
else if (arg_gt_2pi3_fast(z))
{
acb_hypgeom_airy_bound_arg_ge_2pi3(A, (bi != NULL || bip != NULL) ? B : NULL, z, wp);
}
else
{
mag_t A2, B2;
arb_sqrt_ui(acb_imagref(z1), 3, wp); mag_init(A2);
arb_set_si(acb_realref(z1), -1); mag_init(B2);
acb_mul_2exp_si(z1, z1, -1);
/* multiply by exp(-2 pi i / 3) in upper half plane acb_hypgeom_airy_bound_arg_le_2pi3(A, (bi != NULL || bip != NULL) ? B : NULL, z, wp);
and by exp(2 pi i / 3) in lower half plane, to stay close acb_hypgeom_airy_bound_arg_ge_2pi3(A2, (bi != NULL || bip != NULL) ? B2 : NULL, z, wp);
to positive reals */
if (arf_sgn(zim) >= 0)
acb_conj(z1, z1);
acb_mul(z1, z1, z, wp); mag_max(A, A, A2);
acb_neg(zeta, zeta); /* same effect regardless of exp(+/-2 pi i/3) */ mag_max(B, B, A2);
acb_hypgeom_airy_bound_9_7_17(B, z1, zeta); mag_clear(A2);
mag_mul_2exp_si(B, B, 1); mag_clear(B2);
mag_add(B, B, A); }
acb_clear(z1); /* bound |z|^(1/4) */
mag_sqrt(zhi, zhi);
mag_sqrt(zhi, zhi);
/* bound |z|^(-1/4) */
mag_rsqrt(zlo, zlo);
mag_sqrt(zlo, zlo);
if (ai != NULL) mag_mul(ai, A, zlo);
if (aip != NULL) mag_mul(aip, A, zhi);
if (bi != NULL) mag_mul(bi, B, zlo);
if (bip != NULL) mag_mul(bip, B, zhi);
if (near_zero)
{
/* max by bounds on the unit circle */
if (ai != NULL)
{
mag_set_ui_2exp_si(D, 159, -8);
mag_max(ai, ai, D);
}
if (aip != NULL)
{
mag_set_ui_2exp_si(D, 125, -8);
mag_max(aip, aip, D);
}
if (bi != NULL)
{
mag_set_ui_2exp_si(D, 310, -8);
mag_max(bi, bi, D);
}
if (bip != NULL)
{
mag_set_ui_2exp_si(D, 239, -8);
mag_max(bip, bip, D);
}
} }
} }
acb_get_mag(zhi, z);
acb_get_mag_lower(zlo, z);
/* bound |z|^(1/4) */
mag_sqrt(zhi, zhi);
mag_sqrt(zhi, zhi);
/* bound |z|^(-1/4) */
mag_rsqrt(zlo, zlo);
mag_sqrt(zlo, zlo);
if (ai != NULL) mag_mul(ai, A, zlo);
if (aip != NULL) mag_mul(aip, A, zhi);
if (bi != NULL) mag_mul(bi, B, zlo);
if (bip != NULL) mag_mul(bip, B, zhi);
acb_clear(zeta); acb_clear(zeta);
mag_clear(A); mag_clear(A);
mag_clear(B); mag_clear(B);

View file

@ -542,11 +542,25 @@ f_erf_bent(acb_ptr res, const acb_t z, void * param, slong order, slong prec)
return 0; return 0;
} }
/* f(z) = Ai(z) */
int
f_airy_ai(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_airy(res, NULL, NULL, NULL, z, prec);
return 0;
}
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Main test program */ /* Main test program */
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
#define NUM_INTEGRALS 26 #define NUM_INTEGRALS 27
const char * descr[NUM_INTEGRALS] = const char * descr[NUM_INTEGRALS] =
{ {
@ -576,6 +590,7 @@ const char * descr[NUM_INTEGRALS] =
"int_0^{1000} W_0(x) dx", "int_0^{1000} W_0(x) dx",
"int_0^pi max(sin(x), cos(x)) dx", "int_0^pi max(sin(x), cos(x)) dx",
"int_{-1}^1 erf(x/sqrt(0.0002)*0.5+1.5)*exp(-x) dx", "int_{-1}^1 erf(x/sqrt(0.0002)*0.5+1.5)*exp(-x) dx",
"int_{-10}^10 Ai(x) dx",
}; };
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -975,6 +990,12 @@ int main(int argc, char *argv[])
acb_calc_integrate(s, f_erf_bent, NULL, a, b, goal, tol, options, prec); acb_calc_integrate(s, f_erf_bent, NULL, a, b, goal, tol, options, prec);
break; break;
case 26:
acb_set_si(a, -10);
acb_set_si(b, 10);
acb_calc_integrate(s, f_airy_ai, NULL, a, b, goal, tol, options, prec);
break;
default: default:
abort(); abort();
} }