Expand description
Bessel functions at arbitrary real order, starting with the large-x Hankel arm.
Separate from bessel_jy, which is the integer-order family and is
built from fitted minimax rationals. Nothing here uses a coefficient table at any order:
every term comes from a running ratio, which is what makes an arbitrary $\nu$ possible.
§The Hankel expansion, and why it needs no convergence test
J_\nu(x) \sim \sqrt{\frac{2}{\pi x}}\left(P(\nu,x)\cos\omega - Q(\nu,x)\sin\omega\right),
\qquad \omega = x - \left(\tfrac{\nu}{2} + \tfrac{1}{4}\right)\piwith $P$ the even and $Q$ the odd part of $\sum_k a_k(\nu)/x^k$, and
\frac{a_k}{a_{k-1}} = \frac{\mu - (2k-1)^2}{8kx}, \qquad \mu = 4\nu^2This series diverges. The ratio is about $k/2x$, so terms shrink while $k < 2x$ and
grow forever after, and the least term (the floor on achievable accuracy) sits at
$k^{*} = x + \sqrt{x^2 + \nu^2}$ with magnitude around $e^{-2x}$.
So there is nothing to converge to, and the stopping point is an index rather than a
tolerance: the Counted discipline from
iterate, not a convergence test.
It does not use sum_counted, though, and
the reason is worth recording. Producing $Y_\nu$ as well as $J_\nu$ needs $P$ and
$Q$ kept apart, which is two accumulators, and every driver in iterate carries one.
An earlier $J$-only version did fold the two into a single sum by rotating the trig
factor through $\cos, -\sin, -\cos, \sin$, and that worked, but it cannot produce $Y$.
Running the ratio chain twice to get both is worse than carrying one extra accumulator.
That makes four places wanting a paired-accumulator driver: thermite-compensated’s
sin_cos, this, cf2_pq below, and sum_counted’s now-vacant slot.
Boost instead tries the series and returns a bool (hankel_PQ),
bailing when consecutive terms stop halving, which happens at $k \approx x$, only
halfway to the least term. Measured, that costs it about two units of $x$ at small order
and makes the arm unreachable entirely for $\nu \ge 8$, where its guard trips on the very
first term. A try-and-fail arm is also the one shape a packet cannot do cheaply, since
every lane would have to agree on whether the attempt worked.
§Where it is usable, measured rather than assumed
Smallest $x$ reaching 1 eps under optimal truncation, from
notes/special/tools/model_hankel_divergence.py:
$\nu$ | 0 | 1/3 | 1/2 | 1 | 3 | 5 | 8 | 12 |
|---|---|---|---|---|---|---|---|---|
| binary64 | 17.0 | 16.5 | 1.0 | 17.0 | 17.0 | 17.0 | 18.0 | 25.0 |
| binary32 | 6.5 | 6.5 | 1.0 | 6.5 | 7.5 | 7.5 | 11.0 | 24.5 |
Flat in $\nu$ up to about 5, then rising roughly $1.75\nu$ (see
hankel_usable_from). $\nu = 1/2$ is exact at any $x$ because $\mu = (2\nu)^2$ with
$2\nu$ an odd integer makes $\mu - (2k-1)^2$ vanish at $k = \nu - 1/2$ and the series
terminates. That is the same fact as “half-integer order is elementary”, seen from the
asymptotic side.
§Term counts
Terms needed to reach tolerance, worst case at the gate and falling from there, since the stop is at tolerance rather than at the floor:
$x$ | 17 | 20 | 30 | 60 | 300 | 1000 |
|---|---|---|---|---|---|---|
| binary64 | 29 | 20 | 14 | 10 | 7 | 5 |
| binary32 | 5 | 5 | 4 | 4 | 3 | 2 |
N is a const generic so the loop unrolls, and the caller picks it from that table.
Carrying the worst case everywhere costs terms at large $x$ that are already below
epsilon. Harmless numerically, and an open optimization rather than a correctness
question.
Functions§
- bessel_
jy_ real $(J_\nu(x), Y_\nu(x))$at arbitrary real order, over the whole positive axis.- hankel_
jy_ nu $J_\nu(x)$by the Hankel expansion, at arbitrary real order, forxpasthankel_usable_from.- hankel_
usable_ from - The smallest
xat which the Hankel arm reaches full precision for ordernu. - series_
j_ nu $J_\nu(x)$by its ascending series, at arbitrary real order, for smallx.- steed_
jy_ nu $(J_\nu, Y_u, Y_{u+1})$by Steed’s method at the reduced order$u = \nu - n$,$\lvert u\rvert \le 1/2$, for the band between the two other arms.- temme_
y_ nu $(Y_\nu(x), Y_{\nu+1}(x))$by Temme’s series, for smallxand$\lvert\nu\rvert \le 1/2$.