arb/arb_hypgeom/erfinv.c

482 lines
13 KiB
C

/*
Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
*/
#include "arb_hypgeom.h"
#include "arb_fmpz_poly.h"
/* Actually an enclosure for |x| < 0.99. */
static void
arb_erfinv_approx_tiny(arb_t res, const arb_t x, slong prec)
{
arb_t t;
mag_t err;
arb_init(t);
mag_init(err);
arb_get_mag(err, x);
mag_pow_ui(err, err, 3);
arb_const_sqrt_pi(t, prec);
arb_mul_2exp_si(t, t, -1);
arb_mul(res, x, t, prec);
arb_add_error_mag(res, err);
arb_clear(t);
mag_clear(err);
}
/* First terms of asymptotic expansion. */
/* http://www.ams.org/journals/mcom/1976-30-136/S0025-5718-1976-0421040-7/S0025-5718-1976-0421040-7.pdf */
static double erfinv_approx_big(double one_sub_x)
{
double eta, l, y;
eta = -log(one_sub_x*sqrt(3.1415926535897932));
l = log(eta);
y = sqrt(eta - 0.5*l + (0.25*l - 0.5)/eta + (1/16.*l*l - 3/8.*l + 7./8)/(eta*eta)
+ (l*l*l/48 - 7*l*l/32 + 17*l/16 - 107./48)/(eta*eta*eta)
+ (l*l*l*l/128 - 23*l*l*l/192 + 29*l*l/32 - 31*l/8 + 1489/192.)/(eta*eta*eta*eta));
return y;
}
/* First terms of asymptotic expansion, when a double can overflow. */
static void
arb_erfinv_approx_huge(arb_t res, const arb_t one_minus_x, slong prec)
{
arb_t eta, l, y;
fmpz c[5];
arb_ptr poly;
mag_t err;
arb_init(eta);
arb_init(l);
arb_init(y);
mag_init(err);
poly = _arb_vec_init(5);
arb_const_sqrt_pi(eta, prec);
arb_mul(eta, eta, one_minus_x, prec);
arb_log(eta, eta, prec);
arb_neg(eta, eta);
arb_log(l, eta, prec);
arb_mul_ui(y, eta, 12, prec);
arb_inv(y, y, prec);
arb_mul_2exp_si(poly + 0, l, -1);
arb_neg(poly + 0, poly + 0);
c[0] = -2 * 3; c[1] = 3;
_arb_fmpz_poly_evaluate_arb(poly + 1, c, 2, l, prec);
c[0] = 14 * 9; c[1] = -6 * 9; c[2] = 9;
_arb_fmpz_poly_evaluate_arb(poly + 2, c, 3, l, prec);
c[0] = -214 * 18; c[1] = 102 * 18; c[2] = -21 * 18; c[3] = 2 * 18;
_arb_fmpz_poly_evaluate_arb(poly + 3, c, 4, l, prec);
c[0] = 2978 * 54; c[1] = -1488 * 54; c[2] = 348 * 54; c[3] = -46 * 54; c[4] = 3 * 54;
_arb_fmpz_poly_evaluate_arb(poly + 4, c, 5, l, prec);
_arb_poly_evaluate(res, poly, 5, y, prec);
arb_add(res, res, eta, prec);
arb_sqrt(res, res, prec);
/* Should be an enclosure for 1-x < 1e-300 (but not proved). */
arb_get_mag(err, res);
mag_mul_2exp_si(err, err, -50);
arb_clear(eta);
arb_clear(l);
arb_clear(y);
mag_clear(err);
_arb_vec_clear(poly, 5);
}
/* Adapted from https://people.maths.ox.ac.uk/gilesm/codes/erfinv/
with permission */
/* Only good up to about 1 - 1e-15 */
/* Todo: use a good approximation for erfcinv */
static double erfinv_approx(double x, double one_sub_x)
{
double w, p;
w = -log(one_sub_x * (1.0 + x));
if (w < 6.250000)
{
w = w - 3.125000;
p = -3.6444120640178196996e-21;
p = -1.685059138182016589e-19 + p*w;
p = 1.2858480715256400167e-18 + p*w;
p = 1.115787767802518096e-17 + p*w;
p = -1.333171662854620906e-16 + p*w;
p = 2.0972767875968561637e-17 + p*w;
p = 6.6376381343583238325e-15 + p*w;
p = -4.0545662729752068639e-14 + p*w;
p = -8.1519341976054721522e-14 + p*w;
p = 2.6335093153082322977e-12 + p*w;
p = -1.2975133253453532498e-11 + p*w;
p = -5.4154120542946279317e-11 + p*w;
p = 1.051212273321532285e-09 + p*w;
p = -4.1126339803469836976e-09 + p*w;
p = -2.9070369957882005086e-08 + p*w;
p = 4.2347877827932403518e-07 + p*w;
p = -1.3654692000834678645e-06 + p*w;
p = -1.3882523362786468719e-05 + p*w;
p = 0.0001867342080340571352 + p*w;
p = -0.00074070253416626697512 + p*w;
p = -0.0060336708714301490533 + p*w;
p = 0.24015818242558961693 + p*w;
p = 1.6536545626831027356 + p*w;
}
else if (w < 16.000000)
{
w = sqrt(w) - 3.250000;
p = 2.2137376921775787049e-09;
p = 9.0756561938885390979e-08 + p*w;
p = -2.7517406297064545428e-07 + p*w;
p = 1.8239629214389227755e-08 + p*w;
p = 1.5027403968909827627e-06 + p*w;
p = -4.013867526981545969e-06 + p*w;
p = 2.9234449089955446044e-06 + p*w;
p = 1.2475304481671778723e-05 + p*w;
p = -4.7318229009055733981e-05 + p*w;
p = 6.8284851459573175448e-05 + p*w;
p = 2.4031110387097893999e-05 + p*w;
p = -0.0003550375203628474796 + p*w;
p = 0.00095328937973738049703 + p*w;
p = -0.0016882755560235047313 + p*w;
p = 0.0024914420961078508066 + p*w;
p = -0.0037512085075692412107 + p*w;
p = 0.005370914553590063617 + p*w;
p = 1.0052589676941592334 + p*w;
p = 3.0838856104922207635 + p*w;
}
else
{
w = sqrt(w) - 5.000000;
p = -2.7109920616438573243e-11;
p = -2.5556418169965252055e-10 + p*w;
p = 1.5076572693500548083e-09 + p*w;
p = -3.7894654401267369937e-09 + p*w;
p = 7.6157012080783393804e-09 + p*w;
p = -1.4960026627149240478e-08 + p*w;
p = 2.9147953450901080826e-08 + p*w;
p = -6.7711997758452339498e-08 + p*w;
p = 2.2900482228026654717e-07 + p*w;
p = -9.9298272942317002539e-07 + p*w;
p = 4.5260625972231537039e-06 + p*w;
p = -1.9681778105531670567e-05 + p*w;
p = 7.5995277030017761139e-05 + p*w;
p = -0.00021503011930044477347 + p*w;
p = -0.00013871931833623122026 + p*w;
p = 1.0103004648645343977 + p*w;
p = 4.8499064014085844221 + p*w;
}
return p*x;
}
/* floating-point approximation of erfinv(x), to be validated */
static void
arb_hypgeom_erfinv_guess(arb_t res, const arb_t x, const arb_t one_sub_x, slong extraprec)
{
if (arf_cmpabs_2exp_si(arb_midref(x), -30) < 0)
{
arb_erfinv_approx_tiny(res, x, 128);
}
else if (arf_cmpabs_2exp_si(arb_midref(one_sub_x), -52) >= 0)
{
double y;
y = erfinv_approx(arf_get_d(arb_midref(x), ARF_RND_NEAR),
arf_get_d(arb_midref(one_sub_x), ARF_RND_NEAR));
arf_set_d(arb_midref(res), y);
mag_set_d(arb_radref(res), ldexp(y, -50));
}
else if (arf_cmpabs_2exp_si(arb_midref(one_sub_x), -1000) >= 0)
{
double t, y;
t = arf_get_d(arb_midref(one_sub_x), ARF_RND_NEAR);
y = erfinv_approx_big(t);
arf_set_d(arb_midref(res), y);
mag_set_d(arb_radref(res), ldexp(y, -26 + 0.1 * log(t)));
}
else
{
arb_erfinv_approx_huge(res, one_sub_x, 30 + extraprec);
}
}
void
arb_hypgeom_erfinv_precise(arb_t res, const arb_t x, const arb_t one_sub_x, int near_one, slong prec)
{
slong wp;
arb_t f, fprime, root, mid, t;
slong extraprec, goal;
int validated;
if (arb_is_zero(x))
{
arb_zero(res);
return;
}
arb_init(f);
arb_init(fprime);
arb_init(root);
arb_init(mid);
arb_init(t);
goal = prec * 1.001 + 5;
extraprec = fmpz_bits(ARF_EXPREF(arb_midref(one_sub_x)));
extraprec += 15;
/* Start with a guess */
arb_hypgeom_erfinv_guess(root, x, one_sub_x, extraprec);
validated = 0;
while (!validated || arb_rel_accuracy_bits(root) <= goal)
{
/* We should get double the accuracy. */
wp = arb_rel_accuracy_bits(root) * 2 + extraprec;
/* But don't set the precision unrealistically high. */
wp = FLINT_MIN(wp, 4 * (goal + extraprec));
/* In case of quadratic convergence, avoid doing the
penultimate iteration at higher precision than needed. */
if (validated && wp < goal && wp > 0.7 * goal + 2 * extraprec)
wp = goal / 2 + 2 * extraprec;
arb_set(mid, root);
mag_zero(arb_radref(mid));
/* f(y) = erf(y) - x OR (1 - x) - erfc(y) */
/* 1/f'(y) = exp(y^2) * sqrt(pi)/2 */
if (near_one)
{
arb_hypgeom_erfc(f, mid, wp);
arb_sub(f, one_sub_x, f, wp);
}
else
{
arb_hypgeom_erf(f, mid, wp);
arb_sub(f, f, x, wp);
}
arb_sqr(fprime, root, wp);
arb_exp(fprime, fprime, wp);
arb_const_sqrt_pi(t, wp);
arb_mul(fprime, fprime, t, wp);
arb_mul_2exp_si(fprime, fprime, -1);
arb_mul(t, f, fprime, wp);
arb_sub(t, mid, t, wp);
if (arb_contains_interior(root, t))
{
/* Interval Newton proves inclusion. */
/* printf("newton %d -> 1 %ld: ", validated, wp); arb_printd(t, 50); printf("\n"); */
validated = 1;
arb_swap(root, t);
}
else
{
/* Try to improve the guess with a floating-point Newton step. */
/* printf("newton %d -> 0 %ld: ", validated, wp); arb_printd(t, 50); printf("\n"); */
arb_sqr(fprime, mid, wp);
arb_exp(fprime, fprime, wp);
arb_const_sqrt_pi(t, wp);
arb_mul(fprime, fprime, t, wp);
arb_mul_2exp_si(fprime, fprime, -1);
arb_mul(t, f, fprime, wp);
/* The Newton correction is a good guess for the error. */
arb_get_mag(arb_radref(root), t);
mag_mul_2exp_si(arb_radref(root), arb_radref(root), 1);
arb_sub(t, mid, t, wp);
arf_swap(arb_midref(root), arb_midref(t));
/* Use more working precision to get out of any numerical difficulties */
extraprec = extraprec * 1.05 + 10;
validated = 0;
}
/* Something went wrong. Could fall back to bisection if we
want to guarantee convergence? */
if (extraprec > 10 * prec + 10000)
{
arb_indeterminate(root);
break;
}
}
arb_set_round(res, root, prec);
arb_clear(f);
arb_clear(fprime);
arb_clear(root);
arb_clear(mid);
arb_clear(t);
}
static slong
arb_adjust_precision(slong prec, slong acc)
{
acc = FLINT_MIN(acc, prec);
acc = FLINT_MAX(acc, 0);
prec = FLINT_MIN(prec, acc + MAG_BITS);
prec = FLINT_MAX(prec, 2);
return prec;
}
void
arb_hypgeom_erfinv(arb_t res, const arb_t x, slong prec)
{
arb_t x1;
int near_one;
if (arb_is_zero(x))
{
arb_zero(res);
return;
}
if (arf_sgn(arb_midref(x)) < 0)
{
arb_neg(res, x);
arb_hypgeom_erfinv(res, res, prec);
arb_neg(res, res);
return;
}
if (arb_is_one(x))
{
arb_pos_inf(res);
return;
}
arb_init(x1);
near_one = ARF_EXP(arb_midref(x)) == 0;
if (near_one)
{
arb_sub_ui(x1, x, 1, ARF_PREC_EXACT);
arb_neg(x1, x1);
}
else
{
arb_sub_ui(x1, x, 1, prec + 30);
arb_neg(x1, x1);
}
if (arb_is_positive(x1))
{
mag_t err;
slong acc;
arb_t xm;
mag_init(err);
arb_init(xm);
/* Propagated error bound based on derivative. */
/* erfinv'(x) <= (1/2) sqrt(pi) / (1 - |x|) */
arb_get_mag_lower(err, x1);
mag_inv(err, err);
mag_mul(err, err, arb_radref(x));
mag_mul_ui(err, err, 227);
mag_mul_2exp_si(err, err, -8);
acc = arb_rel_accuracy_bits(x);
prec = arb_adjust_precision(prec, acc);
arb_get_mid_arb(xm, x);
if (near_one)
{
arb_sub_ui(x1, xm, 1, ARF_PREC_EXACT);
arb_neg(x1, x1);
}
else
{
arb_sub_ui(x1, xm, 1, prec + 30);
arb_neg(x1, x1);
}
arb_hypgeom_erfinv_precise(res, xm, x1, near_one, prec);
arb_add_error_mag(res, err);
mag_clear(err);
arb_clear(xm);
}
else
{
arb_indeterminate(res);
}
arb_clear(x1);
}
void
arb_hypgeom_erfcinv(arb_t res, const arb_t x1, slong prec)
{
arb_t x;
if (arb_is_one(x1))
{
arb_zero(res);
return;
}
arb_init(x);
if (arf_cmp_d(arb_midref(x1), 0.01) <= 0 && arb_is_positive(x1))
{
mag_t err;
slong acc;
arb_t x1m, xm;
mag_init(err);
arb_init(x1m);
arb_init(xm);
/* Propagated error bound based on derivative. */
/* erfinv'(x) <= (1/2) sqrt(pi) / (1 - |x|) */
arb_get_mag_lower(err, x1);
mag_inv(err, err);
mag_mul(err, err, arb_radref(x1));
mag_mul_ui(err, err, 227);
mag_mul_2exp_si(err, err, -8);
acc = arb_rel_accuracy_bits(x1);
prec = arb_adjust_precision(prec, acc);
arb_get_mid_arb(x1m, x1);
arb_sub_ui(xm, x1m, 1, 2 * prec + 100);
arb_neg(xm, xm);
arb_hypgeom_erfinv_precise(res, xm, x1m, 1, prec);
arb_add_error_mag(res, err);
mag_clear(err);
arb_clear(xm);
arb_clear(x1m);
}
else
{
arb_sub_ui(x, x1, 1, 2 * prec + 100);
arb_neg(x, x);
arb_hypgeom_erfinv(res, x, prec);
}
arb_clear(x);
}