complex Lambert W function (acb_lambertw)

This commit is contained in:
Fredrik Johansson 2017-03-20 18:57:53 +01:00
parent b30933ace9
commit d77b6c5c73
7 changed files with 1121 additions and 0 deletions

12
acb.h
View file

@ -391,6 +391,13 @@ acb_get_rad_ubound_arf(arf_t u, const acb_t z, slong prec)
arf_mul_2exp_si(u, u, 1); arf_mul_2exp_si(u, u, 1);
} }
ACB_INLINE void
acb_union(acb_t res, const acb_t x, const acb_t y, slong prec)
{
arb_union(acb_realref(res), acb_realref(x), acb_realref(y), prec);
arb_union(acb_imagref(res), acb_imagref(x), acb_imagref(y), prec);
}
void acb_arg(arb_t r, const acb_t z, slong prec); void acb_arg(arb_t r, const acb_t z, slong prec);
void acb_sgn(acb_t res, const acb_t z, slong prec); void acb_sgn(acb_t res, const acb_t z, slong prec);
@ -782,6 +789,11 @@ void acb_polylog_si(acb_t w, slong s, const acb_t z, slong prec);
void acb_agm1(acb_t m, const acb_t z, slong prec); void acb_agm1(acb_t m, const acb_t z, slong prec);
void acb_agm1_cpx(acb_ptr m, const acb_t z, slong len, slong prec); void acb_agm1_cpx(acb_ptr m, const acb_t z, slong len, slong prec);
void acb_lambertw_asymp(acb_t res, const acb_t z, const fmpz_t k, slong L, slong M, slong prec);
int acb_lambertw_check_branch(const acb_t w, const fmpz_t k, slong prec);
void acb_lambertw_bound_deriv(mag_t res, const acb_t z, const acb_t ez1, const fmpz_t k);
void acb_lambertw(acb_t res, const acb_t z, const fmpz_t k, int flags, slong prec);
ACB_INLINE void ACB_INLINE void
acb_sqr(acb_t res, const acb_t val, slong prec) acb_sqr(acb_t res, const acb_t val, slong prec)
{ {

593
acb/lambertw.c Normal file
View file

@ -0,0 +1,593 @@
/*
Copyright (C) 2017 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 "acb.h"
/* Check if z crosses a branch cut. */
int
acb_lambertw_branch_crossing(const acb_t z, const acb_t ez1, const fmpz_t k)
{
if (arb_contains_zero(acb_imagref(z)) && !arb_is_nonnegative(acb_imagref(z)))
{
if (fmpz_is_zero(k))
{
if (!arb_is_positive(acb_realref(ez1)))
{
return 1;
}
}
else if (!arb_is_positive(acb_realref(z)))
{
return 1;
}
}
return 0;
}
/* todo: remove radii */
void
acb_lambertw_halley_step(acb_t res, const acb_t z, const acb_t w, slong prec)
{
acb_t ew, t, u, v;
acb_init(ew);
acb_init(t);
acb_init(u);
acb_init(v);
acb_exp(ew, w, prec);
acb_add_ui(u, w, 2, prec);
acb_add_ui(v, w, 1, prec);
acb_mul_2exp_si(v, v, 1);
acb_div(v, u, v, prec);
acb_mul(t, ew, w, prec);
acb_sub(u, t, z, prec);
acb_mul(v, v, u, prec);
acb_neg(v, v);
acb_add(v, v, t, prec);
acb_add(v, v, ew, prec);
acb_div(t, u, v, prec);
acb_sub(t, w, t, prec);
acb_swap(res, t);
acb_clear(ew);
acb_clear(t);
acb_clear(u);
acb_clear(v);
}
/* assumes no aliasing of w and p */
void
acb_lambertw_branchpoint_series(acb_t w, const acb_t t, int bound, slong prec)
{
slong i;
static const int coeffs[] = {-130636800,130636800,-43545600,19958400,
-10402560,5813640,-3394560,2042589,-1256320};
acb_zero(w);
for (i = 8; i >= 0; i--)
{
acb_mul(w, w, t, prec);
acb_add_si(w, w, coeffs[i], prec);
}
acb_div_si(w, w, -coeffs[0], prec);
if (bound)
{
mag_t err;
mag_init(err);
acb_get_mag(err, t);
mag_geom_series(err, err, 9);
if (acb_is_real(t))
arb_add_error_mag(acb_realref(w), err);
else
acb_add_error_mag(w, err);
mag_clear(err);
}
}
void
acb_lambertw_principal_d(acb_t res, const acb_t z)
{
double za, zb, wa, wb, ewa, ewb, t, u, q, r;
int k, maxk = 15;
za = arf_get_d(arb_midref(acb_realref(z)), ARF_RND_DOWN);
zb = arf_get_d(arb_midref(acb_imagref(z)), ARF_RND_DOWN);
/* make sure we end up on the right branch */
if (za < -0.367 && zb > -1e-20 && zb <= 0.0
&& arf_sgn(arb_midref(acb_imagref(z))) < 0)
zb = -1e-20;
wa = za;
wb = zb;
if (fabs(wa) > 2.0 || fabs(wb) > 2.0)
{
t = atan2(wb, wa);
wa = 0.5 * log(wa * wa + wb * wb);
wb = t;
}
else if (fabs(wa) > 0.25 || fabs(wb) > 0.25)
{
/* We have W(z) ~= -1 + (2(ez+1))^(1/2) near the branch point.
Changing the exponent to 1/4 gives a much worse local guess
which however does the job on a larger domain. */
wa *= 5.43656365691809;
wb *= 5.43656365691809;
wa += 2.0;
t = atan2(wb, wa);
r = pow(wa * wa + wb * wb, 0.125);
wa = r * cos(0.25 * t);
wb = r * sin(0.25 * t);
wa -= 1.0;
}
for (k = 0; k < maxk; k++)
{
t = exp(wa);
ewa = t * cos(wb);
ewb = t * sin(wb);
t = (ewa * wa - ewb * wb); q = t + ewa; t -= za;
u = (ewb * wa + ewa * wb); r = u + ewb; u -= zb;
ewa = q * t + r * u; ewb = q * u - r * t;
r = 1.0 / (q * q + r * r);
ewa *= r; ewb *= r;
if ((ewa*ewa + ewb*ewb) < (wa*wa + wb*wb) * 1e-12)
maxk = FLINT_MIN(maxk, k + 2);
wa -= ewa; wb -= ewb;
}
acb_set_d_d(res, wa, wb);
}
void
acb_lambertw_initial_asymp(acb_t w, const acb_t z, const fmpz_t k, slong prec)
{
acb_t L1, L2, t;
acb_init(L1);
acb_init(L2);
acb_init(t);
acb_const_pi(L2, prec);
acb_mul_2exp_si(L2, L2, 1);
acb_mul_fmpz(L2, L2, k, prec);
acb_mul_onei(L2, L2);
acb_log(L1, z, prec);
acb_add(L1, L1, L2, prec);
acb_log(L2, L1, prec);
/* L1 - L2 + L2/L1 + L2(L2-2)/(2 L1^2) */
acb_inv(t, L1, prec);
acb_mul_2exp_si(w, L2, 1);
acb_submul(w, L2, L2, prec);
acb_neg(w, w);
acb_mul(w, w, t, prec);
acb_mul_2exp_si(w, w, -1);
acb_add(w, w, L2, prec);
acb_mul(w, w, t, prec);
acb_sub(w, w, L2, prec);
acb_add(w, w, L1, prec);
acb_clear(L1);
acb_clear(L2);
acb_clear(t);
}
/* assumes no aliasing */
slong
acb_lambertw_initial(acb_t res, const acb_t z, const acb_t ez1, const fmpz_t k, slong prec)
{
/* Handle z very close to 0 on the principal branch. */
if (fmpz_is_zero(k) &&
(arf_cmpabs_2exp_si(arb_midref(acb_realref(z)), -20) <= 0 &&
arf_cmpabs_2exp_si(arb_midref(acb_imagref(z)), -20) <= 0))
{
acb_set(res, z);
acb_submul(res, res, res, prec);
return 40; /* could be tightened... */
}
/* For moderate input not close to the branch point, compute a double
approximation as the initial value. */
if (fmpz_is_zero(k) &&
arf_cmpabs_2exp_si(arb_midref(acb_realref(z)), 400) < 0 &&
arf_cmpabs_2exp_si(arb_midref(acb_imagref(z)), 400) < 0 &&
(arf_cmp_d(arb_midref(acb_realref(z)), -0.37) < 0 ||
arf_cmp_d(arb_midref(acb_realref(z)), -0.36) > 0 ||
arf_cmpabs_d(arb_midref(acb_imagref(z)), 0.01) > 0))
{
acb_lambertw_principal_d(res, z);
return 48;
}
/* Check if we are close to the branch point at -1/e. */
if ((fmpz_is_zero(k) || (fmpz_is_one(k) && arb_is_negative(acb_imagref(z)))
|| (fmpz_equal_si(k, -1) && arb_is_nonnegative(acb_imagref(z))))
&& ((arf_cmpabs_2exp_si(arb_midref(acb_realref(ez1)), -2) <= 0 &&
arf_cmpabs_2exp_si(arb_midref(acb_imagref(ez1)), -2) <= 0)))
{
acb_t t;
acb_init(t);
acb_mul_2exp_si(t, ez1, 1);
mag_zero(arb_radref(acb_realref(t)));
mag_zero(arb_radref(acb_imagref(t)));
acb_mul_ui(t, t, 3, prec);
acb_sqrt(t, t, prec);
if (!fmpz_is_zero(k))
acb_neg(t, t);
acb_lambertw_branchpoint_series(res, t, 0, prec);
acb_clear(t);
return 1; /* todo: estimate */
}
acb_lambertw_initial_asymp(res, z, k, prec);
return 1; /* todo: estimate */
}
/* note: z should be exact here */
void acb_lambertw_main(acb_t res, const acb_t z,
const acb_t ez1, const fmpz_t k, int flags, slong prec)
{
acb_t w, t;
mag_t err;
slong i, wp, accuracy, ebits, kbits, mbits, wp_initial, extraprec;
acb_init(t);
acb_init(w);
mag_init(err);
/* We need higher precision for large k, large exponents, or very close
to the branch point at -1/e. todo: we should be recomputing
ez1 to higher precision when close... */
acb_get_mag(err, z);
if (fmpz_is_zero(k) && mag_cmp_2exp_si(err, 0) < 0)
ebits = 0;
else
ebits = fmpz_bits(MAG_EXPREF(err));
acb_get_mag(err, ez1);
mbits = fmpz_bits(MAG_EXPREF(err));
kbits = fmpz_bits(k);
extraprec = FLINT_MAX(ebits, kbits);
extraprec = FLINT_MAX(extraprec, mbits);
wp = wp_initial = 40 + extraprec;
accuracy = acb_lambertw_initial(w, z, ez1, k, wp_initial);
mag_zero(arb_radref(acb_realref(w)));
mag_zero(arb_radref(acb_imagref(w)));
for (i = 0; i < 5 + FLINT_BIT_COUNT(prec + extraprec); i++)
{
/* todo: should we restart? */
if (!acb_is_finite(w))
break;
wp = FLINT_MIN(3 * accuracy, 1.1 * prec + 10);
wp = FLINT_MAX(wp, 40);
wp += extraprec;
acb_lambertw_halley_step(t, z, w, wp);
/* estimate the error (conservatively) */
acb_sub(w, w, t, wp);
acb_get_mag(err, w);
acb_set(w, t);
acb_add_error_mag(t, err);
accuracy = acb_rel_accuracy_bits(t);
if (accuracy > 2 * extraprec)
accuracy *= 2.9; /* less conservatively */
accuracy = FLINT_MIN(accuracy, wp);
accuracy = FLINT_MAX(accuracy, 0);
if (accuracy > prec + extraprec)
break;
mag_zero(arb_radref(acb_realref(w)));
mag_zero(arb_radref(acb_imagref(w)));
}
wp = FLINT_MIN(3 * accuracy, 1.1 * prec + 10);
wp = FLINT_MAX(wp, 40);
wp += extraprec;
if (acb_lambertw_check_branch(w, k, wp))
{
acb_t u, r, eu1;
mag_t err, rad;
acb_init(u);
acb_init(r);
acb_init(eu1);
mag_init(err);
mag_init(rad);
/* todo: avoid recomputing e^w */
acb_exp(t, w, wp);
acb_mul(t, t, w, wp);
acb_sub(r, t, z, wp);
/* Bound W' on the straight line path between t and z */
acb_union(u, t, z, wp);
arb_const_e(acb_realref(eu1), wp);
arb_zero(acb_imagref(eu1));
acb_mul(eu1, eu1, u, wp);
acb_add_ui(eu1, eu1, 1, wp);
if (acb_lambertw_branch_crossing(u, eu1, k))
{
mag_inf(err);
}
else
{
acb_lambertw_bound_deriv(err, u, eu1, k);
acb_get_mag(rad, r);
mag_mul(err, err, rad);
}
acb_add_error_mag(w, err);
acb_set(res, w);
acb_clear(u);
acb_clear(r);
acb_clear(eu1);
mag_clear(err);
mag_clear(rad);
}
else
{
acb_indeterminate(res);
}
acb_clear(t);
acb_clear(w);
mag_clear(err);
}
void
acb_lambertw_cleared_cut(acb_t res, const acb_t z, const fmpz_t k, int flags, slong prec)
{
acb_t ez1;
acb_init(ez1);
/* compute z*e + 1 */
arb_const_e(acb_realref(ez1), prec);
acb_mul(ez1, ez1, z, prec);
acb_add_ui(ez1, ez1, 1, prec);
if (acb_is_exact(z))
{
acb_lambertw_main(res, z, ez1, k, flags, prec);
}
else
{
acb_t zz;
mag_t err, rad;
mag_init(err);
mag_init(rad);
acb_init(zz);
acb_lambertw_bound_deriv(err, z, ez1, k);
mag_hypot(rad, arb_radref(acb_realref(z)), arb_radref(acb_imagref(z)));
mag_mul(err, err, rad);
acb_set(zz, z);
mag_zero(arb_radref(acb_realref(zz)));
mag_zero(arb_radref(acb_imagref(zz))); /* todo: recompute ez1? */
acb_lambertw_main(res, zz, ez1, k, flags, prec);
acb_add_error_mag(res, err);
mag_clear(err);
mag_clear(rad);
acb_clear(zz);
}
acb_clear(ez1);
}
void
_acb_lambertw(acb_t res, const acb_t z, const acb_t ez1, const fmpz_t k, int flags, slong prec)
{
slong goal, ebits, ebits2, ls, lt;
const fmpz * expo;
/* Estimated accuracy goal. */
/* todo: account for exponent bits and bits in k. */
goal = acb_rel_accuracy_bits(z);
goal = FLINT_MAX(goal, 10);
goal = FLINT_MIN(goal, prec);
/* Handle tiny z directly. For k >= 2, |c_k| <= 4^k / 16. */
if (fmpz_is_zero(k)
&& arf_cmpabs_2exp_si(arb_midref(acb_realref(z)), -goal / 2) < 0
&& arf_cmpabs_2exp_si(arb_midref(acb_imagref(z)), -goal / 2) < 0)
{
mag_t err;
mag_init(err);
acb_get_mag(err, z);
mag_mul_2exp_si(err, err, 2);
acb_set(res, z);
acb_submul(res, res, res, prec);
mag_geom_series(err, err, 3);
mag_mul_2exp_si(err, err, -4);
acb_add_error_mag(res, err);
mag_clear(err);
return;
}
if (arf_cmpabs(arb_midref(acb_realref(z)), arb_midref(acb_imagref(z))) >= 0)
expo = ARF_EXPREF(arb_midref(acb_realref(z)));
else
expo = ARF_EXPREF(arb_midref(acb_imagref(z)));
ebits = fmpz_bits(expo);
/* ebits ~= log2(|log(z) + 2 pi i k|) */
/* ebits2 ~= log2(log(log(z))) */
ebits = FLINT_MAX(ebits, fmpz_bits(k));
ebits = FLINT_MAX(ebits, 1) - 1;
ebits2 = FLINT_BIT_COUNT(ebits);
ebits2 = FLINT_MAX(ebits2, 1) - 1;
/* We gain accuracy from the exponent when W ~ log - log log */
if (fmpz_sgn(expo) > 0 || (fmpz_sgn(expo) < 0 && !fmpz_is_zero(k)))
{
goal += ebits - ebits2;
goal = FLINT_MAX(goal, 10);
goal = FLINT_MIN(goal, prec);
/* The asymptotic series with truncation L, M gives us about
t - max(2+lt+L*(2+ls), M*(2+lt)) bits of accuracy where
ls = -ebits, lt = ebits2 - ebits. */
ls = 2 - ebits;
lt = 2 + ebits2 - ebits;
if (ebits - FLINT_MAX(lt + 1*ls, 1*lt) > goal)
{
acb_lambertw_asymp(res, z, k, 1, 1, goal);
acb_set_round(res, res, prec);
return;
}
else if (ebits - FLINT_MAX(lt + 3*ls, 5*lt) > goal)
{
acb_lambertw_asymp(res, z, k, 3, 5, goal);
acb_set_round(res, res, prec);
return;
}
}
/* Extremely close to the branch point at -1/e, use the series expansion directly. */
if (fmpz_is_zero(k) || (fmpz_is_one(k) && arb_is_negative(acb_imagref(z)))
|| (fmpz_equal_si(k, -1) && arb_is_nonnegative(acb_imagref(z))))
{
if (acb_contains_zero(ez1) ||
(arf_cmpabs_2exp_si(arb_midref(acb_realref(ez1)), -goal / 4.5) < 0 &&
arf_cmpabs_2exp_si(arb_midref(acb_imagref(ez1)), -goal / 4.5) < 0))
{
acb_t t;
acb_init(t);
acb_mul_2exp_si(t, ez1, 1);
acb_sqrt(t, t, goal);
if (!fmpz_is_zero(k))
acb_neg(t, t);
acb_lambertw_branchpoint_series(res, t, 1, goal);
acb_set_round(res, res, prec);
acb_clear(t);
return;
}
}
/* todo: compute union of two results */
if (acb_lambertw_branch_crossing(z, ez1, k))
{
acb_indeterminate(res);
}
else
{
acb_t zz, zmid, zmide1;
arf_t eps;
acb_init(zz);
acb_init(zmid);
acb_init(zmide1);
arf_init(eps);
arf_mul_2exp_si(eps, arb_midref(acb_realref(z)), -goal);
acb_set(zz, z);
if (arf_sgn(arb_midref(acb_realref(zz))) < 0 &&
(!fmpz_is_zero(k) || arf_sgn(arb_midref(acb_realref(ez1))) < 0) &&
arf_cmpabs(arb_midref(acb_imagref(zz)), eps) < 0)
{
/* now the value must be in [0,2eps] */
arf_get_mag(arb_radref(acb_imagref(zz)), eps);
arf_set_mag(arb_midref(acb_imagref(zz)), arb_radref(acb_imagref(zz)));
if (arf_sgn(arb_midref(acb_imagref(z))) >= 0)
{
acb_lambertw_cleared_cut(res, zz, k, flags, goal);
}
else
{
fmpz_t kk;
fmpz_init(kk);
fmpz_neg(kk, k);
acb_lambertw_cleared_cut(res, zz, kk, flags, goal);
acb_conj(res, res);
fmpz_clear(kk);
}
}
else
{
acb_lambertw_cleared_cut(res, zz, k, flags, goal);
}
acb_set_round(res, res, prec);
acb_clear(zz);
acb_clear(zmid);
acb_clear(zmide1);
arf_clear(eps);
}
}
void
acb_lambertw(acb_t res, const acb_t z, const fmpz_t k, int flags, slong prec)
{
acb_t ez1;
if (!acb_is_finite(z) || (!fmpz_is_zero(k) && acb_contains_zero(z)))
{
acb_indeterminate(res);
return;
}
acb_init(ez1);
/* precompute z*e + 1 */
arb_const_e(acb_realref(ez1), prec);
acb_mul(ez1, ez1, z, prec);
acb_add_ui(ez1, ez1, 1, prec);
/* use real code when possible */
if (acb_is_real(z) && arb_is_positive(acb_realref(ez1)) &&
(fmpz_is_zero(k) ||
(fmpz_equal_si(k, -1) && arb_is_negative(acb_realref(z)))))
{
arb_lambertw(acb_realref(res), acb_realref(z), !fmpz_is_zero(k), prec);
arb_zero(acb_imagref(res));
}
else
{
_acb_lambertw(res, z, ez1, k, flags, prec);
}
acb_clear(ez1);
}

144
acb/lambertw_asymp.c Normal file
View file

@ -0,0 +1,144 @@
/*
Copyright (C) 2017 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 "acb.h"
void
acb_lambertw_asymp(acb_t res, const acb_t z, const fmpz_t k, slong L, slong M, slong prec)
{
acb_t L1, L2, sigma, tau, s, c, u;
slong l, m;
fmpz_t t;
fmpz * sc;
/* For k = 0, the asymptotic expansion is not valid near 0. */
/* (It is sufficient to look at the midpoint as a test here.) */
if (fmpz_is_zero(k) && arf_cmpabs_2exp_si(arb_midref(acb_realref(z)), 0) < 0
&& arf_cmpabs_2exp_si(arb_midref(acb_imagref(z)), 0) < 0)
{
acb_indeterminate(res);
return;
}
acb_init(L1);
acb_init(L2);
acb_init(sigma);
acb_init(tau);
acb_init(s);
acb_init(c);
acb_init(u);
fmpz_init(t);
acb_const_pi(L2, prec);
acb_mul_2exp_si(L2, L2, 1);
acb_mul_fmpz(L2, L2, k, prec);
acb_mul_onei(L2, L2);
acb_log(L1, z, prec);
acb_add(L1, L1, L2, prec);
acb_log(L2, L1, prec);
acb_inv(sigma, L1, prec);
acb_mul(tau, L2, sigma, prec);
acb_zero(s);
/* Stirling numbers */
sc = _fmpz_vec_init(L);
acb_one(u);
for (m = 1; m < M; m++)
{
if (m == 1)
{
for (l = 0; l < L; l++)
fmpz_one(sc + l);
}
else
{
for (l = 0; l < L; l++)
{
fmpz_mul_ui(sc + l, sc + l, m + l - 1);
if (l > 0)
fmpz_add(sc + l, sc + l, sc + l - 1);
}
}
acb_zero(c);
/* todo: precompute powers instead of horner */
for (l = L - 1; l >= 0; l--)
{
acb_mul(c, c, sigma, prec);
if (l % 2)
acb_sub_fmpz(c, c, sc + l, prec);
else
acb_add_fmpz(c, c, sc + l, prec);
}
acb_mul(u, u, tau, prec);
acb_div_ui(u, u, m, prec);
acb_addmul(s, c, u, prec);
}
_fmpz_vec_clear(sc, L);
acb_sub(s, s, L2, prec);
acb_add(s, s, L1, prec);
{
mag_t m4s, m4t, one, q, r;
mag_init(m4s);
mag_init(m4t);
mag_init(one);
mag_init(q);
mag_init(r);
acb_get_mag(m4s, sigma);
mag_mul_2exp_si(m4s, m4s, 2);
acb_get_mag(m4t, tau);
mag_mul_2exp_si(m4t, m4t, 2);
mag_one(one);
mag_sub_lower(q, one, m4s);
mag_sub_lower(r, one, m4t);
mag_mul(q, q, r);
mag_pow_ui(r, m4s, L);
mag_mul(r, r, m4t);
mag_pow_ui(m4t, m4t, M);
mag_add(r, r, m4t);
mag_div(q, r, q);
acb_add_error_mag(s, q);
mag_clear(m4s);
mag_clear(m4t);
mag_clear(one);
mag_clear(q);
mag_clear(r);
}
acb_set(res, s);
acb_clear(sigma);
acb_clear(tau);
acb_clear(s);
acb_clear(c);
acb_clear(L1);
acb_clear(L2);
acb_clear(u);
fmpz_clear(t);
}

View file

@ -0,0 +1,90 @@
/*
Copyright (C) 2017 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 "acb.h"
void
acb_lambertw_bound_deriv(mag_t res, const acb_t z, const acb_t ez1, const fmpz_t k)
{
mag_t t, u;
mag_init(t);
mag_init(u);
/* Main approximation: W'(z) ~ 1/|z| */
if (fmpz_is_zero(k))
{
/* 1/(1+|z|) */
acb_get_mag_lower(res, z);
mag_one(t);
mag_add_lower(res, res, t);
mag_div(res, t, res);
}
else if (fmpz_is_pm1(k))
{
/* 2/|z| */
mag_set_ui(t, 2);
acb_get_mag_lower(res, z);
mag_div(res, t, res);
}
else
{
/* |W'(z)| = |1/z| |W(z)/(1+W(z))|
<= |1/z| (2pi) / (2pi-1) */
mag_set_ui_2exp_si(t, 77, -6);
acb_get_mag_lower(res, z);
mag_div(res, t, res);
}
/* Compute correction near the branch point */
if (fmpz_is_zero(k) || fmpz_equal_si(k,-1) ||
(fmpz_is_one(k) && !arb_is_nonnegative(acb_imagref(z))))
{
acb_t b;
acb_init(b);
if (fmpz_is_zero(k))
{
/* [-4,1] + [-2,2]i */
arf_set_si_2exp_si(arb_midref(acb_realref(b)), -3, -1);
mag_set_ui_2exp_si(arb_radref(acb_realref(b)), 5, -1);
arf_zero(arb_midref(acb_imagref(b)));
mag_set_ui(arb_radref(acb_imagref(b)), 2);
}
else
{
/* k = 1 [-1/2,-1/4] + [-1/8,0) i */
/* k = -1 [-1/2,-1/4] + [0,1/8] i */
arf_set_si_2exp_si(arb_midref(acb_realref(b)), -3, -3);
mag_set_ui_2exp_si(arb_radref(acb_realref(b)), 1, -3);
if (fmpz_is_one(k))
arf_set_si_2exp_si(arb_midref(acb_imagref(b)), -1, -4);
else
arf_set_ui_2exp_si(arb_midref(acb_imagref(b)), 1, -4);
mag_set_ui_2exp_si(arb_radref(acb_imagref(b)), 1, -4);
}
if (acb_overlaps(z, b))
{
/* add 2/sqrt(|ez+1|) */
acb_get_mag_lower(u, ez1);
mag_rsqrt(u, u);
mag_mul_2exp_si(u, u, 1);
mag_add(res, res, u);
}
acb_clear(b);
}
mag_clear(t);
mag_clear(u);
}

View file

@ -0,0 +1,88 @@
/*
Copyright (C) 2017 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 "acb.h"
/* todo: use reduced precision test first */
int
acb_lambertw_check_branch(const acb_t w, const fmpz_t k, slong prec)
{
arb_t t, u, v, a, b;
int res = 0;
arb_init(t);
arb_init(u);
arb_init(v);
arb_init(a);
arb_init(b);
/* t = x sinc(y), v = -cos(y) */
arb_sinc(t, acb_imagref(w), prec);
arb_mul(t, t, acb_realref(w), prec);
arb_cos(v, acb_imagref(w), prec);
arb_neg(v, v);
/* u = y / pi, with conjugate relation for k */
arb_const_pi(u, prec);
arb_div(u, acb_imagref(w), u, prec);
if (fmpz_sgn(k) < 0)
arb_neg(u, u);
if (fmpz_is_zero(k))
{
/* -1 < u < 1 and t > v */
arb_set_si(a, -1);
arb_set_si(b, 1);
if (arb_gt(u, a) && arb_lt(u, b) && arb_gt(t, v))
{
res = 1;
}
}
else
{
arb_set_fmpz(a, k);
arb_abs(a, a);
arb_mul_2exp_si(a, a, 1);
arb_add_ui(b, a, 1, prec);
arb_sub_ui(a, a, 2, prec);
/* if u > 2|k|-2 and u < 2|k|+1 */
if (arb_gt(u, a) && arb_lt(u, b))
{
arb_add_ui(a, a, 1, prec);
arb_sub_ui(b, b, 1, prec);
/* if u > 2|k|-1 and u < 2|k| */
if (arb_gt(u, a) && arb_lt(u, b))
{
res = 1;
}
else if (arb_lt(u, b) && arb_lt(t, v)) /* u < 2|k| and t < v */
{
res = 1;
}
else if (arb_gt(u, a) && arb_gt(t, v)) /* u > 2|k|-1 and t > v */
{
res = 1;
}
}
}
arb_clear(t);
arb_clear(u);
arb_clear(v);
arb_clear(a);
arb_clear(b);
return res;
}

152
acb/test/t-lambertw.c Normal file
View file

@ -0,0 +1,152 @@
/*
Copyright (C) 2017 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 "acb.h"
int main()
{
slong iter;
flint_rand_t state;
flint_printf("lambertw....");
fflush(stdout);
flint_randinit(state);
for (iter = 0; iter < 10000 * arb_test_multiplier(); iter++)
{
acb_t x1, x2, t, w1, w2;
slong prec1, prec2, ebits;
fmpz_t k;
acb_init(x1);
acb_init(x2);
acb_init(t);
acb_init(w1);
acb_init(w2);
fmpz_init(k);
if (n_randint(state, 4) == 0)
{
prec1 = 2 + n_randint(state, 3000);
prec2 = 2 + n_randint(state, 3000);
ebits = 1 + n_randint(state, 1000);
}
else
{
prec1 = 2 + n_randint(state, 300);
prec2 = 2 + n_randint(state, 300);
ebits = 1 + n_randint(state, 50);
}
acb_randtest(x1, state, 1 + n_randint(state, 1000), ebits);
acb_randtest(x2, state, 1 + n_randint(state, 1000), ebits);
acb_randtest(t, state, 1 + n_randint(state, 1000), ebits);
acb_randtest(w1, state, 1 + n_randint(state, 1000), ebits);
acb_randtest(w2, state, 1 + n_randint(state, 1000), ebits);
fmpz_randtest(k, state, ebits);
if (n_randint(state, 4) == 0)
{
arb_const_e(acb_realref(t), 2 * prec1);
arb_inv(acb_realref(t), acb_realref(t), 2 * prec1);
arb_sub(acb_realref(x1), acb_realref(x1), acb_realref(t), 2 * prec1);
}
if (n_randint(state, 2))
{
acb_set(x2, x1);
}
else
{
acb_add(x2, x1, t, 2 * prec1);
acb_sub(x2, x2, t, 2 * prec1);
}
if (n_randint(state, 4) == 0)
acb_lambertw_asymp(w1, x1, k,
1 + n_randint(state, 10), 1 + n_randint(state, 10), prec1);
else
acb_lambertw(w1, x1, k, 0, prec1);
if (n_randint(state, 2))
{
acb_set(w2, x2);
acb_lambertw(w2, w2, k, 0, prec1);
}
else
{
acb_lambertw(w2, x2, k, 0, prec1);
}
if (!acb_overlaps(w1, w2))
{
flint_printf("FAIL: overlap\n\n");
flint_printf("iter %wd, branch = ", iter); fmpz_print(k);
flint_printf("prec1 = %wd, prec2 = %wd\n\n", prec1, prec2);
flint_printf("x1 = "); acb_printd(x1, 50); flint_printf("\n\n");
flint_printf("x2 = "); acb_printd(x2, 50); flint_printf("\n\n");
flint_printf("w1 = "); acb_printd(w1, 50); flint_printf("\n\n");
flint_printf("w2 = "); acb_printd(w2, 50); flint_printf("\n\n");
flint_abort();
}
acb_exp(t, w1, prec1);
acb_mul(t, t, w1, prec1);
if (!acb_contains(t, x1))
{
flint_printf("FAIL: functional equation\n\n");
flint_printf("iter %wd, branch = ", iter); fmpz_print(k);
flint_printf("prec1 = %wd, prec2 = %wd\n\n", prec1, prec2);
flint_printf("x1 = "); acb_printd(x1, 50); flint_printf("\n\n");
flint_printf("x2 = "); acb_printd(x2, 50); flint_printf("\n\n");
flint_printf("w1 = "); acb_printd(w1, 50); flint_printf("\n\n");
flint_printf("w2 = "); acb_printd(w2, 50); flint_printf("\n\n");
flint_printf("t = "); acb_printd(t, 50); flint_printf("\n\n");
flint_abort();
}
if (!arb_contains_zero(acb_imagref(x1)))
{
acb_conj(x2, x1);
fmpz_neg(k, k);
acb_lambertw(w2, x2, k, 0, prec2);
fmpz_neg(k, k);
acb_conj(w2, w2);
if (!acb_overlaps(w1, w2))
{
flint_printf("FAIL: conjugation\n\n");
flint_printf("iter %wd, branch = ", iter); fmpz_print(k);
flint_printf("prec1 = %wd, prec2 = %wd\n\n", prec1, prec2);
flint_printf("x1 = "); acb_printd(x1, 50); flint_printf("\n\n");
flint_printf("x2 = "); acb_printd(x2, 50); flint_printf("\n\n");
flint_printf("w1 = "); acb_printd(w1, 50); flint_printf("\n\n");
flint_printf("w2 = "); acb_printd(w2, 50); flint_printf("\n\n");
flint_abort();
}
}
acb_clear(x1);
acb_clear(x2);
acb_clear(t);
acb_clear(w1);
acb_clear(w2);
fmpz_clear(k);
}
flint_randclear(state);
flint_cleanup();
flint_printf("PASS\n");
return EXIT_SUCCESS;
}

View file

@ -260,6 +260,10 @@ Precision and comparisons
Returns nonzero iff *x* and *y* have some point in common. Returns nonzero iff *x* and *y* have some point in common.
.. function:: void acb_union(acb_t z, const acb_t x, const acb_t y, slong prec)
Sets *z* to a complex interval containing both *x* and *y*.
.. function:: void acb_get_abs_ubound_arf(arf_t u, const acb_t z, slong prec) .. function:: void acb_get_abs_ubound_arf(arf_t u, const acb_t z, slong prec)
Sets *u* to an upper bound for the absolute value of *z*, computed Sets *u* to an upper bound for the absolute value of *z*, computed
@ -672,6 +676,44 @@ Inverse hyperbolic functions
Sets *res* to `\operatorname{atanh}(z) = -i \operatorname{atan}(iz)`. Sets *res* to `\operatorname{atanh}(z) = -i \operatorname{atan}(iz)`.
Lambert W function
-------------------------------------------------------------------------------
.. function:: void acb_lambertw_asymp(acb_t res, const acb_t z, const fmpz_t k, slong L, slong M, slong prec)
Sets *res* to the Lambert W function `W_k(z)` computed using *L* and *M*
terms in the bivariate series giving the asymptotic expansion at
zero or infinity. This algorithm is valid
everywhere, but the error bound is only finite when `|\log(z)|` is
sufficiently large.
.. function:: int acb_lambertw_check_branch(const acb_t w, const fmpz_t k, slong prec)
Tests if *w* definitely lies in the image of the branch `W_k(z)`.
This function is used internally to verify that a computed approximation
of the Lambert W function lies on the intended branch. Note that this will
necessarily evaluate to false for points exactly on (or overlapping) the
branch cuts, where a different algorithm has to be used.
.. function:: void acb_lambertw_bound_deriv(mag_t res, const acb_t z, const acb_t ez1, const fmpz_t k)
Sets *res* to an upper bound for `|W_k'(z)|`. The input *ez1* should
contain the precomputed value of `ez+1`.
Along the real line, the directional derivative of `W_k(z)` is understood
to be taken. As a result, the user must handle the branch cut
discontinuity separately when using this function to bound perturbations
in the value of `W_k(z)`.
.. function:: void acb_lambertw(acb_t res, const acb_t z, const fmpz_t k, int flags, slong prec)
Sets *res* to the Lambert W function `W_k(z)` where the index *k* selects
an arbitrary branch (with `k = 0` giving the principal branch).
The placement of branch cuts follows [CGHJK1996]_.
The *flags* argument is currently unused. In a future version, it might
allow further control over branch cuts.
Rising factorials Rising factorials
------------------------------------------------------------------------------- -------------------------------------------------------------------------------