Skip to main content

complex_bessel/
lib.rs

1//! Pure Rust implementation of complex Bessel functions based on Amos Algorithm 644 (ACM TOMS 644).
2//!
3//! # Features
4//!
5//! - **f32 & f64** — all functions accept `Complex<f64>` or `Complex<f32>`
6//! - **Complete function set** — J, Y, I, K, H<sup>(1)</sup>, H<sup>(2)</sup>, Ai, Ai′, Bi, Bi′
7//! - **Consecutive orders** — `_seq` and `_seq_into` variants evaluate at orders ν, ν+1, …, ν+n−1 in one call
8//! - **Exponential scaling** — `_scaled` variants prevent overflow/underflow
9//! - **Negative orders** — supports ν < 0 via DLMF reflection formulas (not in Amos)
10//! - **`no_std`** — no allocator needed; only the `Vec`-returning `_seq` variants require `std`
11//!
12//! # Quick start
13//!
14//! ```
15//! use complex_bessel::*;
16//! use num_complex::Complex;
17//!
18//! let z = Complex::new(1.0, 2.0);
19//!
20//! let j = besselj(0.5, z).unwrap();
21//! let k = besselk(1.0, z).unwrap();
22//! let h = hankel1(0.0, z).unwrap();
23//!
24//! // Scaled versions prevent overflow/underflow
25//! let k_scaled = besselk_scaled(1.0, z).unwrap();
26//!
27//! // Airy functions
28//! let ai = airy(z).unwrap();
29//! let ai_prime = airyprime(z).unwrap();
30//! ```
31//!
32//! # Functions
33//!
34//! | Function | Description | Scaled variant returns |
35//! |----------|-------------|------------------------|
36//! | [`besselj`]`(ν, z)`<br>[`besselj_scaled`]`(ν, z)`<br>[`besselj_seq`]`(ν, z, n, scaling)`<br>[`besselj_seq_into`]`(ν, z, scaling, out)` | J<sub>ν</sub>(z), Bessel first kind | exp(−\|Im(z)\|) · J<sub>ν</sub>(z) |
37//! | [`bessely`]`(ν, z)`<br>[`bessely_scaled`]`(ν, z)`<br>[`bessely_seq`]`(ν, z, n, scaling)`<br>[`bessely_seq_into`]`(ν, z, scaling, out)` | Y<sub>ν</sub>(z), Bessel second kind | exp(−\|Im(z)\|) · Y<sub>ν</sub>(z) |
38//! | [`besseli`]`(ν, z)`<br>[`besseli_scaled`]`(ν, z)`<br>[`besseli_seq`]`(ν, z, n, scaling)`<br>[`besseli_seq_into`]`(ν, z, scaling, out)` | I<sub>ν</sub>(z), modified first kind | exp(−\|Re(z)\|) · I<sub>ν</sub>(z) |
39//! | [`besselk`]`(ν, z)`<br>[`besselk_scaled`]`(ν, z)`<br>[`besselk_seq`]`(ν, z, n, scaling)`<br>[`besselk_seq_into`]`(ν, z, scaling, out)` | K<sub>ν</sub>(z), modified second kind | exp(z) · K<sub>ν</sub>(z) |
40//! | [`hankel1`]`(ν, z)`<br>[`hankel1_scaled`]`(ν, z)`<br>[`hankel1_seq`]`(ν, z, n, scaling)`<br>[`hankel1_seq_into`]`(ν, z, scaling, out)` | H<sub>ν</sub><sup>(1)</sup>(z), Hankel first kind | exp(−iz) · H<sub>ν</sub><sup>(1)</sup>(z) |
41//! | [`hankel2`]`(ν, z)`<br>[`hankel2_scaled`]`(ν, z)`<br>[`hankel2_seq`]`(ν, z, n, scaling)`<br>[`hankel2_seq_into`]`(ν, z, scaling, out)` | H<sub>ν</sub><sup>(2)</sup>(z), Hankel second kind | exp(iz) · H<sub>ν</sub><sup>(2)</sup>(z) |
42//! | [`airy`]`(z)`<br>[`airy_scaled`]`(z)`<br>[`airy_raw`]`(z, scaling)` | Ai(z), Airy first kind | exp(ζ) · Ai(z) |
43//! | [`airyprime`]`(z)`<br>[`airyprime_scaled`]`(z)`<br>[`airyprime_raw`]`(z, scaling)` | Ai′(z), derivative of Airy first kind | exp(ζ) · Ai′(z) |
44//! | [`biry`]`(z)`<br>[`biry_scaled`]`(z)`<br>[`biry_raw`]`(z, scaling)` | Bi(z), Airy second kind | exp(−\|Re(ζ)\|) · Bi(z) |
45//! | [`biryprime`]`(z)`<br>[`biryprime_scaled`]`(z)`<br>[`biryprime_raw`]`(z, scaling)` | Bi′(z), derivative of Airy second kind | exp(−\|Re(ζ)\|) · Bi′(z) |
46//!
47//! where ζ = (2/3) z√z.
48//!
49//! Functions with a branch cut along the negative real axis (J and I of
50//! non-integer order, Y, K, H, and the factor exp(ζ) of the scaled Ai and Ai′)
51//! take their values there from above the cut (arg z = π), as in Amos: an
52//! imaginary part of −0 is treated like +0.
53//!
54//! ## Exponential scaling
55//!
56//! Each `_scaled` variant multiplies the result by an exponential factor that
57//! cancels the asymptotic growth (or decay), keeping values in a representable
58//! floating-point range. The exact factor is listed in the "Scaled variant
59//! returns" column above. Use scaled functions whenever |z| or |Im(z)| is
60//! large enough that the unscaled result would overflow or underflow.
61//!
62//! # Negative orders
63//!
64//! All functions accept any real order, including negative values.
65//! DLMF reflection formulas are applied automatically:
66//!
67//! - **J**: J<sub>−ν</sub>(z) = cos(νπ) J<sub>ν</sub>(z) − sin(νπ) Y<sub>ν</sub>(z) (DLMF 10.2.3)
68//! - **Y**: Y<sub>−ν</sub>(z) = sin(νπ) J<sub>ν</sub>(z) + cos(νπ) Y<sub>ν</sub>(z) (DLMF 10.2.3)
69//! - **I**: I<sub>−ν</sub>(z) = I<sub>ν</sub>(z) + (2/π) sin(νπ) K<sub>ν</sub>(z) (DLMF 10.27.2)
70//! - **K**: K<sub>−ν</sub>(z) = K<sub>ν</sub>(z) (DLMF 10.27.3)
71//! - **H<sup>(1)</sup>**: H<sup>(1)</sup><sub>−ν</sub>(z) = exp(νπi) H<sup>(1)</sup><sub>ν</sub>(z) (DLMF 10.4.6)
72//! - **H<sup>(2)</sup>**: H<sup>(2)</sup><sub>−ν</sub>(z) = exp(−νπi) H<sup>(2)</sup><sub>ν</sub>(z) (DLMF 10.4.6)
73//!
74//! For integer orders, simplified identities are used (e.g., J<sub>−n</sub>(z) = (−1)<sup>n</sup> J<sub>n</sub>(z)).
75//! For half-integer orders, cos(νπ) = 0, so only one function is computed:
76//! J<sub>−ν</sub>(z) = −sin(νπ) Y<sub>ν</sub>(z) and Y<sub>−ν</sub>(z) = sin(νπ) J<sub>ν</sub>(z).
77//!
78//! # Accuracy on the axes
79//!
80//! Amos's algorithm is accurate normwise: the error is small relative to |f(z)|.
81//! On the real and imaginary axes some values combine two real functions of
82//! very different size, e.g. H<sup>(1)</sup><sub>ν</sub>(x) = J<sub>ν</sub>(x) + iY<sub>ν</sub>(x)
83//! with J ≪ Y for x < ν, and Amos's small component can then be off entirely.
84//! The crate recomputes such a component from the two real functions:
85//!
86//! - H<sup>(1)</sup>, H<sup>(2)</sup> on the positive and negative real axis
87//! - Y on the negative real axis (also scaled)
88//! - K on the imaginary axis
89//! - H<sup>(2)</sup> on the positive and H<sup>(1)</sup> on the negative imaginary axis (also scaled)
90//!
91//! For Y and H<sup>(2)</sup> on the negative real axis this includes parts for
92//! |ν| < x: Re Y<sub>ν</sub>(−x) = cos(νπ) Y<sub>ν</sub>(x) for orders close to a
93//! half-integer, and, for |ν| < 18 next to their zeros, parts that are
94//! Y<sub>|ν|</sub> alone: Re Y<sub>ν</sub>(−x) for ν ≥ 0, and parts at or close to
95//! integer and half-integer orders, e.g. Im H<sup>(2)</sup><sub>n</sub>(−x) =
96//! (−1)<sup>n+1</sup> Y<sub>n</sub>(x). J and Y are exactly real on the positive
97//! real axis.
98//!
99//! A remaining limitation: in the other functions, a small component that is
100//! a nearly vanishing cos(νπ) or sin(νπ) times one real function, for orders
101//! close to an integer or a half-integer, is still only accurate relative to
102//! |f(z)|; its relative error grows roughly like ε/|cos(νπ)| (or
103//! ε/|sin(νπ)|). An example is Re J<sub>ν</sub>(−x) = cos(νπ) J<sub>ν</sub>(x).
104//!
105//! Only values exactly on an axis are recomputed. Next to an axis (e.g.
106//! Im z = 10⁻¹⁰·|z|) the same cancellation remains, less deep the farther z
107//! is from the axis, and the parts are accurate only relative to |f(z)|, as
108//! in Amos.
109//!
110//! For f32, J<sub>ν</sub>(x) on the real axis with ν ≳ 10 and 35 ≲ x ≲ 45 errs
111//! by up to about 7e-3·|H<sub>ν</sub>(x)|, as in Amos's single-precision CBESJ.
112//!
113//! # Function variants
114//!
115//! ## Consecutive orders
116//!
117//! The `_seq` variants ([`besselj_seq`], [`besselk_seq`], …) compute values at
118//! consecutive orders ν, ν+1, …, ν+n−1 in a single call and return
119//! a [`BesselResult`] that includes an [`Accuracy`] field.
120//! The `_seq_into` variants ([`besselj_seq_into`], [`besselk_seq_into`], …)
121//! write the sequence into a caller-provided slice and return a [`SeqInfo`]
122//! with [`Accuracy`]; they never allocate.
123//! The `_raw` Airy variants ([`airy_raw`], [`biry_raw`], …) similarly return
124//! an [`AiryResult`] with [`Accuracy`].
125//! All other functions return only the computed value without [`Accuracy`].
126//!
127//! **[`Accuracy`]:**
128//!
129//! | Status | Meaning |
130//! |--------|---------|
131//! | [`Normal`](Accuracy::Normal) | Full machine precision |
132//! | [`Reduced`](Accuracy::Reduced) | More than half of significant digits may be lost |
133//!
134//! [`Reduced`](Accuracy::Reduced) is extremely rare in practice. SciPy's Bessel wrappers also silently
135//! discard the equivalent Amos IERR=3 flag by default.
136//!
137//! To check the accuracy status, use a `_seq`/`_seq_into` function (Bessel) or a
138//! `_raw` function (Airy):
139//!
140//! ```
141//! use complex_bessel::*;
142//! use num_complex::Complex;
143//!
144//! let z = Complex::new(1.0, 2.0);
145//! let mut out = [Complex::new(0.0, 0.0); 1];
146//! let info = besselk_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
147//! assert!(matches!(info.status, Accuracy::Normal));
148//!
149//! let result = airy_raw(z, Scaling::Unscaled).unwrap();
150//! assert!(matches!(result.status, Accuracy::Normal));
151//! ```
152//!
153//! # Error handling
154//!
155//! All functions return `Result<_, Error>`. The four error variants are:
156//!
157//! | Variant | Cause |
158//! |---------|-------|
159//! | [`InvalidInput`](Error::InvalidInput) | non-finite z or ν; z = 0 for K, H, Y (except at negative half-integer orders) and for J, I of negative non-integer order; n < 1 (empty `out`) |
160//! | [`Overflow`](Error::Overflow) | \|z\| or \|ν\| too large (or \|z\| too small) for a finite result |
161//! | [`TotalPrecisionLoss`](Error::TotalPrecisionLoss) | \|z\| or \|ν\| too large for meaningful computation |
162//! | [`ConvergenceFailure`](Error::ConvergenceFailure) | internal algorithm did not converge |
163//!
164//! # Using without `std`
165//!
166//! Turn off the default `std` feature:
167//!
168//! ```toml
169//! complex-bessel = { version = "0.5", default-features = false }
170//! ```
171//!
172//! All functions except the six `_seq` variants (which return a `Vec`) remain
173//! available, and none of them allocate. Math functions then come from `libm`
174//! instead of the platform library.
175
176#![warn(missing_docs)]
177// Without `std`, the docs still link to the `std`-only `_seq` functions.
178#![cfg_attr(not(feature = "std"), allow(rustdoc::broken_intra_doc_links))]
179#![no_std]
180
181#[cfg(feature = "std")]
182extern crate alloc;
183#[cfg(feature = "std")]
184extern crate std;
185
186pub(crate) mod airy;
187pub(crate) mod algo;
188pub(crate) mod axis;
189pub(crate) mod besh;
190pub(crate) mod besi;
191pub(crate) mod besj;
192pub(crate) mod besk;
193pub(crate) mod besy;
194pub mod machine;
195pub mod types;
196pub(crate) mod utils;
197
198pub use machine::BesselFloat;
199#[cfg(feature = "std")]
200pub use types::BesselResult;
201pub use types::{Accuracy, AiryResult, Error, Scaling, SeqInfo};
202
203use num_complex::Complex;
204use types::{AiryDerivative, HankelKind};
205use utils::{Workspace, with_workspace};
206
207// ── Helper: integer order detection ──
208
209/// Check if `nu` is a non-negative integer. Returns `Some(n)` if so.
210#[inline]
211fn as_integer<T: BesselFloat>(nu: T) -> Option<i64> {
212    if nu == nu.floor() {
213        // Safe conversion: orders beyond i64 range are not practical
214        nu.to_i64()
215    } else {
216        None
217    }
218}
219
220// ── Element-wise reflection helpers (shared by single-value and _seq) ──
221
222/// J_{-ν}(z) = cos(νπ)·J_ν(z) − sin(νπ)·Y_ν(z)  (DLMF 10.2.3)
223#[inline]
224fn reflect_j_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
225    j * utils::cospi(order) - y * utils::sinpi(order)
226}
227
228/// Y_{-ν}(z) = sin(νπ)·J_ν(z) + cos(νπ)·Y_ν(z)  (DLMF 10.2.3)
229#[inline]
230fn reflect_y_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
231    j * utils::sinpi(order) + y * utils::cospi(order)
232}
233
234/// I_{-ν}(z) = I_ν(z) + (2/π)·sin(νπ)·K_ν(z)  (DLMF 10.27.2)
235/// `k_val` must already have scaling correction applied by the caller.
236#[inline]
237fn reflect_i_element<T: BesselFloat>(order: T, i_val: Complex<T>, k_val: Complex<T>) -> Complex<T> {
238    let pi = T::from_f64(core::f64::consts::PI);
239    let two = T::from_f64(2.0);
240    utils::mul_add_scalar(k_val, two / pi * utils::sinpi(order), i_val)
241}
242
243/// H^(m)_{-ν}(z) = exp(±νπi)·H^(m)_ν(z)  (DLMF 10.4.6/7)
244#[inline]
245fn reflect_h_element<T: BesselFloat>(order: T, kind: HankelKind, h: Complex<T>) -> Complex<T> {
246    let cos_nu_pi = utils::cospi(order);
247    let sin_nu_pi = utils::sinpi(order);
248    let rotation = match kind {
249        HankelKind::First => Complex::new(cos_nu_pi, sin_nu_pi),
250        HankelKind::Second => Complex::new(cos_nu_pi, -sin_nu_pi),
251    };
252    h * rotation
253}
254
255/// Convert K scaling to I scaling for the reflection formula.
256/// I_scaled = exp(-|Re(z)|)·I, K_scaled = exp(z)·K.
257/// factor = exp(-|Re(z)|) / exp(z) = exp(-i·Im(z)) · [exp(-2·Re(z)) if Re(z)>0]
258#[inline]
259fn k_to_i_scaling_correction<T: BesselFloat>(z: Complex<T>, k_val: Complex<T>) -> Complex<T> {
260    let two = T::from_f64(2.0);
261    let (sin_a, cos_a) = (-z.im).sin_cos();
262    let mut result = k_val * Complex::new(cos_a, sin_a);
263    if z.re > T::zero() {
264        result = result * (-two * z.re).exp();
265    }
266    result
267}
268
269/// (-1)^n sign factor for integer order reflection.
270#[inline]
271fn integer_sign<T: BesselFloat>(n: i64) -> T {
272    if n % 2 == 0 { T::one() } else { -T::one() }
273}
274
275// ── Internal: compute with given scaling for negative order support ──
276
277#[inline]
278fn besselj_value<T: BesselFloat>(
279    nu: T,
280    z: Complex<T>,
281    scaling: Scaling,
282) -> Result<Complex<T>, Error> {
283    let zero = T::zero();
284    let czero = Complex::new(zero, zero);
285    if nu >= zero {
286        let mut y = [czero];
287        besj::zbesj(z, nu, scaling, &mut y)?;
288        return Ok(y[0]);
289    }
290
291    let abs_nu = nu.abs();
292
293    // Integer shortcut: J_{-n}(z) = (-1)^n * J_n(z)
294    if let Some(n) = as_integer(abs_nu) {
295        let mut y = [czero];
296        besj::zbesj(z, abs_nu, scaling, &mut y)?;
297        return Ok(y[0] * integer_sign::<T>(n));
298    }
299
300    // Half-integer |ν|: cos(νπ) = 0, so J_ν is not needed
301    if utils::cospi(abs_nu) == zero {
302        let mut y_buf = [czero];
303        besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
304        return Ok(reflect_j_element(abs_nu, czero, y_buf[0]));
305    }
306
307    // General case: need both J and Y at positive |ν|
308    let mut j_buf = [czero];
309    let mut y_buf = [czero];
310    besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
311    besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
312
313    Ok(reflect_j_element(abs_nu, j_buf[0], y_buf[0]))
314}
315
316#[inline]
317fn bessely_value<T: BesselFloat>(
318    nu: T,
319    z: Complex<T>,
320    scaling: Scaling,
321) -> Result<Complex<T>, Error> {
322    let zero = T::zero();
323    let czero = Complex::new(zero, zero);
324    if nu >= zero {
325        let mut y = [czero];
326        besy::zbesy(z, nu, scaling, &mut y)?;
327        return Ok(y[0]);
328    }
329
330    let abs_nu = nu.abs();
331
332    // Integer shortcut: Y_{-n}(z) = (-1)^n * Y_n(z)
333    if let Some(n) = as_integer(abs_nu) {
334        let mut y = [czero];
335        besy::zbesy(z, abs_nu, scaling, &mut y)?;
336        return Ok(y[0] * integer_sign::<T>(n));
337    }
338
339    // Half-integer |ν|: cos(νπ) = 0, so Y_ν is not needed (it can overflow,
340    // or be undefined at z = 0, where Y_{−ν} = ±J_ν is finite)
341    if utils::cospi(abs_nu) == zero {
342        let mut j_buf = [czero];
343        besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
344        return Ok(reflect_y_element(abs_nu, j_buf[0], czero));
345    }
346
347    // General case: need both J and Y at positive |ν|
348    let mut j_buf = [czero];
349    let mut y_buf = [czero];
350    besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
351    besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
352
353    Ok(reflect_y_element(abs_nu, j_buf[0], y_buf[0]))
354}
355
356#[inline]
357fn besseli_internal<T: BesselFloat>(
358    nu: T,
359    z: Complex<T>,
360    scaling: Scaling,
361) -> Result<Complex<T>, Error> {
362    let zero = T::zero();
363    let czero = Complex::new(zero, zero);
364    if nu >= zero {
365        let mut y = [czero];
366        besi::zbesi(z, nu, scaling, &mut y)?;
367        return Ok(y[0]);
368    }
369
370    let abs_nu = nu.abs();
371
372    // Integer shortcut: I_{-n}(z) = I_n(z)
373    if as_integer(abs_nu).is_some() {
374        let mut y = [czero];
375        besi::zbesi(z, abs_nu, scaling, &mut y)?;
376        return Ok(y[0]);
377    }
378
379    // General case: need both I and K at positive |ν|
380    let mut i_buf = [czero];
381    let mut k_buf = [czero];
382    besi::zbesi(z, abs_nu, scaling, &mut i_buf)?;
383    besk::zbesk(z, abs_nu, scaling, &mut k_buf)?;
384
385    let mut k_val = k_buf[0];
386    if scaling == Scaling::Exponential {
387        k_val = k_to_i_scaling_correction(z, k_val);
388    }
389
390    Ok(reflect_i_element(abs_nu, i_buf[0], k_val))
391}
392
393#[inline]
394fn besselk_value<T: BesselFloat>(
395    nu: T,
396    z: Complex<T>,
397    scaling: Scaling,
398) -> Result<Complex<T>, Error> {
399    // K_{-ν}(z) = K_ν(z) (DLMF 10.27.3)
400    let abs_nu = nu.abs();
401    let zero = T::zero();
402    let mut y = [Complex::new(zero, zero)];
403    besk::zbesk(z, abs_nu, scaling, &mut y)?;
404    Ok(y[0])
405}
406
407#[inline]
408fn hankel_value<T: BesselFloat>(
409    kind: HankelKind,
410    nu: T,
411    z: Complex<T>,
412    scaling: Scaling,
413) -> Result<Complex<T>, Error> {
414    let zero = T::zero();
415    if nu >= zero {
416        let mut y = [Complex::new(zero, zero)];
417        besh::zbesh(z, nu, kind, scaling, &mut y)?;
418        return Ok(y[0]);
419    }
420
421    let abs_nu = nu.abs();
422    let mut y = [Complex::new(zero, zero)];
423    besh::zbesh(z, abs_nu, kind, scaling, &mut y)?;
424
425    Ok(reflect_h_element(abs_nu, kind, y[0]))
426}
427
428// The functions as computed by Amos (with the reflection formulas for ν < 0),
429// then with the components on the axes made accurate (see `axis`).
430
431#[inline]
432fn besselj_internal<T: BesselFloat>(
433    nu: T,
434    z: Complex<T>,
435    scaling: Scaling,
436) -> Result<Complex<T>, Error> {
437    Ok(axis::real_on_positive_axis(
438        z,
439        besselj_value(nu, z, scaling)?,
440    ))
441}
442
443#[inline]
444fn bessely_internal<T: BesselFloat>(
445    nu: T,
446    z: Complex<T>,
447    scaling: Scaling,
448) -> Result<Complex<T>, Error> {
449    let v = axis::real_on_positive_axis(z, bessely_value(nu, z, scaling)?);
450    Ok(axis::fix_single(axis::AxisFn::Y, nu, z, scaling, v))
451}
452
453#[inline]
454fn besselk_internal<T: BesselFloat>(
455    nu: T,
456    z: Complex<T>,
457    scaling: Scaling,
458) -> Result<Complex<T>, Error> {
459    let v = besselk_value(nu, z, scaling)?;
460    Ok(axis::fix_single(axis::AxisFn::K, nu, z, scaling, v))
461}
462
463#[inline]
464fn hankel_internal<T: BesselFloat>(
465    kind: HankelKind,
466    nu: T,
467    z: Complex<T>,
468    scaling: Scaling,
469) -> Result<Complex<T>, Error> {
470    let v = hankel_value(kind, nu, z, scaling)?;
471    Ok(axis::fix_single(
472        axis::AxisFn::Hankel(kind),
473        nu,
474        z,
475        scaling,
476        v,
477    ))
478}
479
480// ── Single-value convenience functions ──
481
482/// Bessel function of the first kind, J<sub>ν</sub>(z).
483///
484/// Computes a single value of J_ν(z) for complex z and real order ν
485/// (any real value, including negative).
486///
487/// For negative ν, the DLMF 10.2.3 reflection formula is applied:
488/// `J_{-ν}(z) = cos(νπ) J_ν(z) - sin(νπ) Y_ν(z)`.
489/// For negative integer n, `J_{−n}(z) = (−1)^n J_n(z)`; for half-integer ν,
490/// `J_{−ν}(z) = −sin(νπ) Y_ν(z)`.
491///
492/// # Example
493///
494/// ```
495/// use complex_bessel::besselj;
496/// use num_complex::Complex;
497///
498/// let z = Complex::new(1.0_f64, 0.0);
499/// let j0 = besselj(0.0, z).unwrap();
500/// assert!((j0.re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
501/// ```
502///
503/// # Errors
504///
505/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
506/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
507/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
508/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
509#[inline]
510pub fn besselj<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
511    besselj_internal(nu, z, Scaling::Unscaled)
512}
513
514/// Bessel function of the second kind, Y<sub>ν</sub>(z).
515///
516/// Computes a single value of Y_ν(z) for complex z and real order ν
517/// (any real value, including negative).
518///
519/// For negative ν, the DLMF 10.2.3 reflection formula is applied:
520/// `Y_{-ν}(z) = sin(νπ) J_ν(z) + cos(νπ) Y_ν(z)`.
521/// For negative integer n, `Y_{−n}(z) = (−1)^n Y_n(z)`; for half-integer ν,
522/// `Y_{−ν}(z) = sin(νπ) J_ν(z)`.
523///
524/// # Example
525///
526/// ```
527/// use complex_bessel::bessely;
528/// use num_complex::Complex;
529///
530/// let z = Complex::new(1.0_f64, 0.0);
531/// let y0 = bessely(0.0, z).unwrap();
532/// assert!((y0.re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
533/// ```
534///
535/// # Errors
536///
537/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 (except at negative half-integer ν, where Y_ν(0) = 0).
538/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
539/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
540/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
541#[inline]
542pub fn bessely<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
543    bessely_internal(nu, z, Scaling::Unscaled)
544}
545
546/// Modified Bessel function of the first kind, I<sub>ν</sub>(z).
547///
548/// Computes a single value of I_ν(z) for complex z and real order ν
549/// (any real value, including negative).
550///
551/// For negative ν, the DLMF 10.27.2 reflection formula is applied:
552/// `I_{-ν}(z) = I_ν(z) + (2/π) sin(νπ) K_ν(z)`.
553/// For negative integer n, `I_{−n}(z) = I_n(z)`.
554///
555/// # Example
556///
557/// ```
558/// use complex_bessel::besseli;
559/// use num_complex::Complex;
560///
561/// let z = Complex::new(1.0_f64, 0.0);
562/// let i0 = besseli(0.0, z).unwrap();
563/// assert!((i0.re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
564/// ```
565///
566/// # Errors
567///
568/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
569/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
570/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
571/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
572#[inline]
573pub fn besseli<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
574    besseli_internal(nu, z, Scaling::Unscaled)
575}
576
577/// Modified Bessel function of the second kind, K<sub>ν</sub>(z).
578///
579/// Computes a single value of K_ν(z) for complex z and real order ν
580/// (any real value, including negative).
581///
582/// For negative ν, K_{-ν}(z) = K_ν(z) (DLMF 10.27.3).
583///
584/// # Example
585///
586/// ```
587/// use complex_bessel::besselk;
588/// use num_complex::Complex;
589///
590/// let z = Complex::new(1.0_f64, 0.0);
591/// let k0 = besselk(0.0, z).unwrap();
592/// assert!((k0.re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
593/// ```
594///
595/// # Errors
596///
597/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
598/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
599/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
600/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
601#[inline]
602pub fn besselk<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
603    besselk_internal(nu, z, Scaling::Unscaled)
604}
605
606/// Hankel function of the first kind, H<sub>ν</sub><sup>(1)</sup>(z).
607///
608/// Computes a single value of H_ν^(1)(z) for complex z and real order ν
609/// (any real value, including negative).
610///
611/// For negative ν, the DLMF 10.4.6 reflection formula is applied:
612/// `H^(1)_{-ν}(z) = exp(νπi) H^(1)_ν(z)`.
613/// For negative integer n, `H^(1)_{−n}(z) = (−1)^n H^(1)_n(z)`.
614///
615/// # Example
616///
617/// ```
618/// use complex_bessel::hankel1;
619/// use num_complex::Complex;
620///
621/// let z = Complex::new(1.0_f64, 0.0);
622/// let h = hankel1(0.0, z).unwrap();
623/// assert!((h.re - 0.7652).abs() < 1e-3); // Re = J_0(1) ≈ 0.7652
624/// assert!((h.im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
625/// ```
626///
627/// # Errors
628///
629/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
630/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
631/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
632/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
633#[inline]
634pub fn hankel1<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
635    hankel_internal(HankelKind::First, nu, z, Scaling::Unscaled)
636}
637
638/// Hankel function of the second kind, H<sub>ν</sub><sup>(2)</sup>(z).
639///
640/// Computes a single value of H_ν^(2)(z) for complex z and real order ν
641/// (any real value, including negative).
642///
643/// For negative ν, the DLMF 10.4.6 reflection formula is applied:
644/// `H^(2)_{-ν}(z) = exp(-νπi) H^(2)_ν(z)`.
645/// For negative integer n, `H^(2)_{−n}(z) = (−1)^n H^(2)_n(z)`.
646///
647/// # Example
648///
649/// ```
650/// use complex_bessel::hankel2;
651/// use num_complex::Complex;
652///
653/// let z = Complex::new(1.0_f64, 0.0);
654/// let h = hankel2(0.0, z).unwrap();
655/// assert!((h.re - 0.7652).abs() < 1e-3); // Re = J_0(1) ≈ 0.7652
656/// assert!((h.im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
657/// ```
658///
659/// # Errors
660///
661/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
662/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
663/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
664/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
665#[inline]
666pub fn hankel2<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
667    hankel_internal(HankelKind::Second, nu, z, Scaling::Unscaled)
668}
669
670/// Airy function Ai(z).
671///
672/// Computes the Airy function Ai(z) for complex z.
673/// Ai(z) is a solution to the differential equation w′′ − z·w = 0
674/// that decays super-exponentially for large positive real z.
675///
676/// # Example
677///
678/// ```
679/// use complex_bessel::airy;
680/// use num_complex::Complex;
681///
682/// let z = Complex::new(0.0_f64, 0.0);
683/// let ai = airy(z).unwrap();
684/// assert!((ai.re - 0.3550).abs() < 1e-3); // Ai(0) ≈ 0.3550
685/// ```
686///
687/// # Errors
688///
689/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
690/// - [`Error::Overflow`] if |z| is too large for a finite result.
691/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
692/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
693#[inline]
694pub fn airy<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
695    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Unscaled)?;
696    Ok(result)
697}
698
699/// Derivative of the Airy function, Ai'(z).
700///
701/// Computes the derivative of Ai(z) for complex z.
702/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
703/// Satisfies the differential equation Ai′′(z) = z · Ai(z).
704///
705/// # Example
706///
707/// ```
708/// use complex_bessel::airyprime;
709/// use num_complex::Complex;
710///
711/// let z = Complex::new(0.0_f64, 0.0);
712/// let ai_prime = airyprime(z).unwrap();
713/// assert!((ai_prime.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
714/// ```
715///
716/// # Errors
717///
718/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
719/// - [`Error::Overflow`] if |z| is too large for a finite result.
720/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
721/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
722#[inline]
723pub fn airyprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
724    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
725    Ok(result)
726}
727
728/// Airy function of the second kind, Bi(z).
729///
730/// Computes the Airy function Bi(z) for complex z.
731/// Bi(z) is the solution to w′′ − z·w = 0 that grows super-exponentially
732/// for large positive real z.
733///
734/// # Example
735///
736/// ```
737/// use complex_bessel::biry;
738/// use num_complex::Complex;
739///
740/// let z = Complex::new(0.0_f64, 0.0);
741/// let bi = biry(z).unwrap();
742/// assert!((bi.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
743/// ```
744///
745/// # Errors
746///
747/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
748/// - [`Error::Overflow`] if |z| is too large for a finite result.
749/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
750/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
751#[inline]
752pub fn biry<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
753    let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Unscaled)?;
754    Ok(result)
755}
756
757/// Derivative of the Airy function of the second kind, Bi'(z).
758///
759/// Computes the derivative of Bi(z) for complex z.
760/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
761/// Satisfies the differential equation Bi′′(z) = z · Bi(z).
762///
763/// # Example
764///
765/// ```
766/// use complex_bessel::biryprime;
767/// use num_complex::Complex;
768///
769/// let z = Complex::new(0.0_f64, 0.0);
770/// let bi_prime = biryprime(z).unwrap();
771/// assert!((bi_prime.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
772/// ```
773///
774/// # Errors
775///
776/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
777/// - [`Error::Overflow`] if |z| is too large for a finite result.
778/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
779/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
780#[inline]
781pub fn biryprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
782    let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
783    Ok(result)
784}
785
786// ── Scaled single-value functions ──
787
788/// Scaled Bessel function of the first kind, exp(−|Im(z)|) · J<sub>ν</sub>(z).
789///
790/// The exponential factor cancels the asymptotic growth of J for large imaginary
791/// arguments, keeping results in a representable floating-point range.
792///
793/// Supports negative ν via the same reflection formula as [`besselj`].
794///
795/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
796///
797/// # Example
798///
799/// ```
800/// use complex_bessel::besselj_scaled;
801/// use num_complex::Complex;
802///
803/// let z = Complex::new(0.0_f64, 10.0);
804/// let j_s = besselj_scaled(0.0, z).unwrap();
805/// assert!((j_s.re - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * J_0(10i) ≈ 0.1278
806/// ```
807///
808/// # Errors
809///
810/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
811/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
812/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
813/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
814#[inline]
815pub fn besselj_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
816    besselj_internal(nu, z, Scaling::Exponential)
817}
818
819/// Scaled Bessel function of the second kind, exp(−|Im(z)|) · Y<sub>ν</sub>(z).
820///
821/// The exponential factor cancels the asymptotic growth of Y for large imaginary
822/// arguments, keeping results in a representable floating-point range.
823///
824/// Supports negative ν via the same reflection formula as [`bessely`].
825///
826/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
827///
828/// # Example
829///
830/// ```
831/// use complex_bessel::bessely_scaled;
832/// use num_complex::Complex;
833///
834/// let z = Complex::new(0.0_f64, 10.0);
835/// let y_s = bessely_scaled(0.0, z).unwrap();
836/// assert!((y_s.im - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * Y_0(10i), Im ≈ 0.1278
837/// ```
838///
839/// # Errors
840///
841/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 (except at negative half-integer ν, where Y_ν(0) = 0).
842/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
843/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
844/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
845#[inline]
846pub fn bessely_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
847    bessely_internal(nu, z, Scaling::Exponential)
848}
849
850/// Scaled modified Bessel function of the first kind, exp(−|Re(z)|) · I<sub>ν</sub>(z).
851///
852/// I_ν(z) grows exponentially for large |Re(z)|, so the unscaled value can
853/// easily overflow. The scaling factor exp(−|Re(z)|) keeps the result finite.
854///
855/// Supports negative ν via the same reflection formula as [`besseli`].
856///
857/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
858///
859/// # Example
860///
861/// ```
862/// use complex_bessel::besseli_scaled;
863/// use num_complex::Complex;
864///
865/// let z = Complex::new(10.0_f64, 0.0);
866/// let i_s = besseli_scaled(0.0, z).unwrap();
867/// assert!((i_s.re - 0.1278).abs() < 1e-3); // exp(-10) * I_0(10) ≈ 0.1278
868/// ```
869///
870/// # Errors
871///
872/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
873/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
874/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
875/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
876#[inline]
877pub fn besseli_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
878    besseli_internal(nu, z, Scaling::Exponential)
879}
880
881/// Scaled modified Bessel function of the second kind, exp(z) · K<sub>ν</sub>(z).
882///
883/// K_ν(z) decays exponentially for large Re(z), so unscaled values can underflow
884/// to zero. The scaling factor exp(z) keeps the result in a normal range.
885///
886/// Supports negative ν via the same reflection formula as [`besselk`].
887///
888/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
889///
890/// # Example
891///
892/// ```
893/// use complex_bessel::besselk_scaled;
894/// use num_complex::Complex;
895///
896/// let z = Complex::new(500.0_f64, 0.0);
897///
898/// // K_0(500) would underflow to 0, but the scaled version stays finite:
899/// let k_s = besselk_scaled(0.0, z).unwrap();
900/// assert!((k_s.re - 0.0560).abs() < 1e-3); // exp(500) * K_0(500) ≈ 0.0560
901/// ```
902///
903/// # Errors
904///
905/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
906/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
907/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
908/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
909#[inline]
910pub fn besselk_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
911    besselk_internal(nu, z, Scaling::Exponential)
912}
913
914/// Scaled Hankel function of the first kind, exp(−iz) · H<sub>ν</sub><sup>(1)</sup>(z).
915///
916/// H^(1) grows exponentially in the lower half-plane;
917/// the scaling factor removes this growth, preventing overflow.
918///
919/// Supports negative ν via the same reflection formula as [`hankel1`].
920///
921/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
922///
923/// # Example
924///
925/// ```
926/// use complex_bessel::hankel1_scaled;
927/// use num_complex::Complex;
928///
929/// let z = Complex::new(1.0_f64, 0.0);
930/// let h_s = hankel1_scaled(0.0, z).unwrap();
931/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(-i) * H^(1)_0(1), Re ≈ 0.4877
932/// ```
933///
934/// # Errors
935///
936/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
937/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
938/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
939/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
940#[inline]
941pub fn hankel1_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
942    hankel_internal(HankelKind::First, nu, z, Scaling::Exponential)
943}
944
945/// Scaled Hankel function of the second kind, exp(iz) · H<sub>ν</sub><sup>(2)</sup>(z).
946///
947/// H^(2) grows exponentially in the upper half-plane;
948/// the scaling factor removes this growth, preventing overflow.
949///
950/// Supports negative ν via the same reflection formula as [`hankel2`].
951///
952/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
953///
954/// # Example
955///
956/// ```
957/// use complex_bessel::hankel2_scaled;
958/// use num_complex::Complex;
959///
960/// let z = Complex::new(1.0_f64, 0.0);
961/// let h_s = hankel2_scaled(0.0, z).unwrap();
962/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(i) * H^(2)_0(1), Re ≈ 0.4877
963/// ```
964///
965/// # Errors
966///
967/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
968/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
969/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
970/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
971#[inline]
972pub fn hankel2_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
973    hankel_internal(HankelKind::Second, nu, z, Scaling::Exponential)
974}
975
976/// Scaled Airy function, exp(ζ) · Ai(z), where ζ = (2/3) z√z.
977///
978/// Ai(z) decays super-exponentially for large positive real z.
979/// The scaling factor exp(ζ) keeps the result representable.
980///
981/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
982///
983/// # Example
984///
985/// ```
986/// use complex_bessel::airy_scaled;
987/// use num_complex::Complex;
988///
989/// let z = Complex::new(5.0_f64, 0.0);
990/// let ai_s = airy_scaled(z).unwrap();
991/// assert!((ai_s.re - 0.1870).abs() < 1e-3); // exp(ζ) * Ai(5) ≈ 0.1870
992/// ```
993///
994/// # Errors
995///
996/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
997/// - [`Error::Overflow`] if |z| is too large for a finite result.
998/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
999/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1000#[inline]
1001pub fn airy_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
1002    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Exponential)?;
1003    Ok(result)
1004}
1005
1006/// Scaled derivative of the Airy function, exp(ζ) · Ai'(z), where ζ = (2/3) z√z.
1007///
1008/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
1009/// The scaling factor exp(ζ) keeps the result representable.
1010///
1011/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
1012///
1013/// # Example
1014///
1015/// ```
1016/// use complex_bessel::airyprime_scaled;
1017/// use num_complex::Complex;
1018///
1019/// let z = Complex::new(5.0_f64, 0.0);
1020/// let ai_s = airyprime_scaled(z).unwrap();
1021/// assert!((ai_s.re + 0.4270).abs() < 1e-3); // exp(ζ) * Ai'(5) ≈ -0.4270
1022/// ```
1023///
1024/// # Errors
1025///
1026/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1027/// - [`Error::Overflow`] if |z| is too large for a finite result.
1028/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1029/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1030#[inline]
1031pub fn airyprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
1032    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Exponential)?;
1033    Ok(result)
1034}
1035
1036/// Scaled Airy function of the second kind, exp(−|Re(ζ)|) · Bi(z), where ζ = (2/3) z√z.
1037///
1038/// Bi(z) grows super-exponentially for large positive real z.
1039/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
1040///
1041/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
1042///
1043/// # Example
1044///
1045/// ```
1046/// use complex_bessel::biry_scaled;
1047/// use num_complex::Complex;
1048///
1049/// let z = Complex::new(5.0_f64, 0.0);
1050/// let bi_s = biry_scaled(z).unwrap();
1051/// assert!((bi_s.re - 0.3811).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi(5) ≈ 0.3811
1052/// ```
1053///
1054/// # Errors
1055///
1056/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1057/// - [`Error::Overflow`] if |z| is too large for a finite result.
1058/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1059/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1060#[inline]
1061pub fn biry_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
1062    let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Exponential)?;
1063    Ok(result)
1064}
1065
1066/// Scaled derivative of the Airy function of the second kind, exp(−|Re(ζ)|) · Bi'(z), where ζ = (2/3) z√z.
1067///
1068/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
1069/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
1070///
1071/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
1072///
1073/// # Example
1074///
1075/// ```
1076/// use complex_bessel::biryprime_scaled;
1077/// use num_complex::Complex;
1078///
1079/// let z = Complex::new(5.0_f64, 0.0);
1080/// let bi_s = biryprime_scaled(z).unwrap();
1081/// assert!((bi_s.re - 0.8319).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi'(5) ≈ 0.8319
1082/// ```
1083///
1084/// # Errors
1085///
1086/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1087/// - [`Error::Overflow`] if |z| is too large for a finite result.
1088/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1089/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1090#[inline]
1091pub fn biryprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
1092    let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Exponential)?;
1093    Ok(result)
1094}
1095
1096// ── Airy _raw functions (expose Accuracy) ──
1097
1098/// Airy function Ai(z) with accuracy status.
1099///
1100/// Like [`airy`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1101/// - [`Accuracy::Normal`] — no significant precision loss
1102/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1103///
1104/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1105/// see [crate-level docs](crate#exponential-scaling) for details.
1106///
1107/// # Example
1108///
1109/// ```
1110/// use complex_bessel::*;
1111/// use num_complex::Complex;
1112///
1113/// let z = Complex::new(0.0_f64, 0.0);
1114/// let result = airy_raw(z, Scaling::Unscaled).unwrap();
1115/// assert!((result.value.re - 0.3550).abs() < 1e-3); // Ai(0) ≈ 0.3550
1116/// assert!(matches!(result.status, Accuracy::Normal));
1117/// ```
1118///
1119/// # Errors
1120///
1121/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1122/// - [`Error::Overflow`] if |z| is too large for a finite result.
1123/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1124/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1125#[inline]
1126pub fn airy_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1127    let (value, _nz, status) = airy::zairy(z, AiryDerivative::Value, scaling)?;
1128    Ok(AiryResult { value, status })
1129}
1130
1131/// Derivative of the Airy function Ai'(z) with accuracy status.
1132///
1133/// Like [`airyprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1134/// - [`Accuracy::Normal`] — no significant precision loss
1135/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1136///
1137/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1138/// see [crate-level docs](crate#exponential-scaling) for details.
1139///
1140/// # Example
1141///
1142/// ```
1143/// use complex_bessel::*;
1144/// use num_complex::Complex;
1145///
1146/// let z = Complex::new(0.0_f64, 0.0);
1147/// let result = airyprime_raw(z, Scaling::Unscaled).unwrap();
1148/// assert!((result.value.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
1149/// assert!(matches!(result.status, Accuracy::Normal));
1150/// ```
1151///
1152/// # Errors
1153///
1154/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1155/// - [`Error::Overflow`] if |z| is too large for a finite result.
1156/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1157/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1158#[inline]
1159pub fn airyprime_raw<T: BesselFloat>(
1160    z: Complex<T>,
1161    scaling: Scaling,
1162) -> Result<AiryResult<T>, Error> {
1163    let (value, _nz, status) = airy::zairy(z, AiryDerivative::Derivative, scaling)?;
1164    Ok(AiryResult { value, status })
1165}
1166
1167/// Airy function of the second kind Bi(z) with accuracy status.
1168///
1169/// Like [`biry`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1170/// - [`Accuracy::Normal`] — no significant precision loss
1171/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1172///
1173/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1174/// see [crate-level docs](crate#exponential-scaling) for details.
1175///
1176/// # Example
1177///
1178/// ```
1179/// use complex_bessel::*;
1180/// use num_complex::Complex;
1181///
1182/// let z = Complex::new(0.0_f64, 0.0);
1183/// let result = biry_raw(z, Scaling::Unscaled).unwrap();
1184/// assert!((result.value.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
1185/// assert!(matches!(result.status, Accuracy::Normal));
1186/// ```
1187///
1188/// # Errors
1189///
1190/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1191/// - [`Error::Overflow`] if |z| is too large for a finite result.
1192/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1193/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1194#[inline]
1195pub fn biry_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1196    let (value, status) = airy::zbiry(z, AiryDerivative::Value, scaling)?;
1197    Ok(AiryResult { value, status })
1198}
1199
1200/// Derivative of the Airy function of the second kind Bi'(z) with accuracy status.
1201///
1202/// Like [`biryprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1203/// - [`Accuracy::Normal`] — no significant precision loss
1204/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1205///
1206/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1207/// see [crate-level docs](crate#exponential-scaling) for details.
1208///
1209/// # Example
1210///
1211/// ```
1212/// use complex_bessel::*;
1213/// use num_complex::Complex;
1214///
1215/// let z = Complex::new(0.0_f64, 0.0);
1216/// let result = biryprime_raw(z, Scaling::Unscaled).unwrap();
1217/// assert!((result.value.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
1218/// assert!(matches!(result.status, Accuracy::Normal));
1219/// ```
1220///
1221/// # Errors
1222///
1223/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1224/// - [`Error::Overflow`] if |z| is too large for a finite result.
1225/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1226/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1227#[inline]
1228pub fn biryprime_raw<T: BesselFloat>(
1229    z: Complex<T>,
1230    scaling: Scaling,
1231) -> Result<AiryResult<T>, Error> {
1232    let (value, status) = airy::zbiry(z, AiryDerivative::Derivative, scaling)?;
1233    Ok(AiryResult { value, status })
1234}
1235
1236// ── Sequence functions ──
1237
1238/// Take the worse of two statuses (Reduced > Normal).
1239#[inline]
1240fn worse_status(a: Accuracy, b: Accuracy) -> Accuracy {
1241    match (a, b) {
1242        (Accuracy::Reduced, _) | (_, Accuracy::Reduced) => Accuracy::Reduced,
1243        _ => Accuracy::Normal,
1244    }
1245}
1246
1247/// Underflow count of a reflected sequence: its zero elements. Underflow
1248/// can be at either end (large |orders| below zero, high orders above), so
1249/// all of them are counted (Amos also counts the exact zeros at z = 0).
1250#[inline]
1251fn recount_underflow<T: BesselFloat>(values: &[Complex<T>]) -> usize {
1252    let zero = Complex::new(T::zero(), T::zero());
1253    values.iter().filter(|v| **v == zero).count()
1254}
1255
1256/// Compute the number of negative-order elements (before crossing zero).
1257/// For orders ν, ν+1, …, ν+n−1, this is the count where ν+j < 0.
1258#[inline]
1259fn neg_count<T: BesselFloat>(nu: T, n: usize) -> usize {
1260    if nu >= T::zero() {
1261        return 0;
1262    }
1263    // Number of j in [0,n) with nu + j < 0
1264    let abs_nu = nu.abs();
1265    // ceil(|nu|) gives the first j where nu+j >= 0
1266    let c = abs_nu.ceil();
1267    let cn = if let Some(v) = c.to_usize() { v } else { n };
1268    cn.min(n)
1269}
1270
1271/// Upper-interface signature shared by the sequence helpers below:
1272/// `f(start, buf)` fills `buf` with the function at orders start, start+1, ….
1273type SeqFn<'a, T> = dyn FnMut(T, &mut [Complex<T>]) -> Result<(usize, Accuracy), Error> + 'a;
1274
1275// ── Negative-order sequences ──
1276//
1277// Orders ν, ν+1, …, ν+n−1 with ν < 0 are computed at |order| and reflected.
1278// Everything happens in `out`; only the secondary function of the non-integer
1279// reflection formulas (none for half-integer J and Y, where cos(νπ) = 0) and
1280// Y's K values need a workspace: the full length on
1281// the heap for `_seq`, or a fixed stack buffer filled in chunks for `_seq_into`.
1282
1283/// Set `out[j]` = f(|ν+j|) when the |orders| form a single lattice: all
1284/// orders negative (nc == n), or ν an integer whose orders cross zero.
1285///
1286/// For a crossing, the orders 0..=max(|ν|, ν+n−1) always fit in `out`
1287/// (n = nc + number of orders ≥ 0), so one call computes them in place and
1288/// they are mirrored into position.
1289#[allow(clippy::needless_range_loop)]
1290fn fill_abs_orders<T: BesselFloat>(
1291    out: &mut [Complex<T>],
1292    nu: T,
1293    nc: usize,
1294    f: &mut SeqFn<'_, T>,
1295) -> Result<Accuracy, Error> {
1296    let n = out.len();
1297    if nc == n {
1298        // |orders| = |ν|-(n-1), …, |ν| ascending, then reversed into place
1299        let start = nu.abs() - T::from_f64((n - 1) as f64);
1300        let (_, status) = f(start, out)?;
1301        out.reverse();
1302        return Ok(status);
1303    }
1304    // Integer crossing, e.g. ν=-2, n=5 → orders -2,-1,0,1,2; nc = |ν|
1305    let pos_n = n - nc; // orders 0, 1, …, pos_n-1
1306    if pos_n > nc {
1307        // Largest |order| is positive: compute 0..pos_n into the positive part,
1308        // then mirror f(nc-j) into out[j]
1309        let (_, status) = f(T::zero(), &mut out[nc..])?;
1310        for j in 0..nc {
1311            out[j] = out[2 * nc - j];
1312        }
1313        Ok(status)
1314    } else {
1315        // Largest |order| is |ν|: compute 0..=nc, reverse so out[j] = f(nc-j),
1316        // then mirror f(m) into the positive tail
1317        let (_, status) = f(T::zero(), &mut out[..=nc])?;
1318        out[..=nc].reverse();
1319        for m in 1..pos_n {
1320            out[nc + m] = out[nc - m];
1321        }
1322        Ok(status)
1323    }
1324}
1325
1326/// Set `out[..nc]` = f(|ν+j|) and `out[nc..]` = f(ν+j) for a non-integer ν
1327/// whose orders cross zero: two lattices, two calls.
1328fn fill_non_int_crossing<T: BesselFloat>(
1329    out: &mut [Complex<T>],
1330    nu: T,
1331    nc: usize,
1332    f: &mut SeqFn<'_, T>,
1333) -> Result<Accuracy, Error> {
1334    let abs_nu = nu.abs();
1335    // Negative part: |orders| frac, frac+1, …, |ν| ascending, then reversed
1336    let frac_neg = abs_nu - abs_nu.floor();
1337    let (_, status1) = f(frac_neg, &mut out[..nc])?;
1338    out[..nc].reverse();
1339    // Positive part: orders ν+nc, …, ν+n-1
1340    let pos_start = nu + T::from_f64(nc as f64);
1341    let (_, status2) = f(pos_start, &mut out[nc..])?;
1342    Ok(worse_status(status1, status2))
1343}
1344
1345/// Apply a two-function reflection formula to the negative non-integer orders
1346/// −(|ν| − j), j = 0, …, len−1, stored in `seg`.
1347///
1348/// Their |orders| form the lattice lo, lo+1, …, |ν| with lo = |ν| − (len−1),
1349/// computed in ascending order and then reversed. `whole` computes one
1350/// function for the whole lattice into `seg`; `chunked` computes the other
1351/// into `buf`, `buf.len()` orders at a time. With `chunked_first`, the first
1352/// chunk is computed before `whole` (to keep the call order of the formula).
1353/// `combine(|order|, whole, chunked)` returns the reflected value.
1354fn reflect_lattice<T: BesselFloat>(
1355    seg: &mut [Complex<T>],
1356    abs_nu: T,
1357    chunked_first: bool,
1358    whole: &mut SeqFn<'_, T>,
1359    chunked: &mut SeqFn<'_, T>,
1360    combine: &dyn Fn(T, Complex<T>, Complex<T>) -> Complex<T>,
1361    buf: &mut [Complex<T>],
1362) -> Result<Accuracy, Error> {
1363    let len = seg.len();
1364    let chunk = buf.len();
1365    assert!(chunk > 0, "empty workspace");
1366    // |ν| minus an integer below it stays on the ulp grid of |ν|: exact
1367    let lo = abs_nu - T::from_f64((len - 1) as f64);
1368    let mut status = Accuracy::Normal;
1369    let mut whole_done = false;
1370    if !chunked_first {
1371        let (_, s) = whole(lo, seg)?;
1372        status = s;
1373        whole_done = true;
1374    }
1375    let mut c0 = 0;
1376    while c0 < len {
1377        let c1 = (c0 + chunk).min(len);
1378        // Exact for the same reason as `lo`
1379        let start = lo + T::from_f64(c0 as f64);
1380        let (_, s) = chunked(start, &mut buf[..c1 - c0])?;
1381        status = worse_status(status, s);
1382        if !whole_done {
1383            let (_, s) = whole(lo, seg)?;
1384            status = worse_status(status, s);
1385            whole_done = true;
1386        }
1387        for k in c0..c1 {
1388            let order = abs_nu - T::from_f64((len - 1 - k) as f64);
1389            seg[k] = combine(order, seg[k], buf[k - c0]);
1390        }
1391        c0 = c1;
1392    }
1393    seg.reverse();
1394    Ok(status)
1395}
1396
1397/// Multiply the negative integer orders by (−1)^|order| (J and Y).
1398fn apply_integer_sign<T: BesselFloat>(out: &mut [Complex<T>], nu: T) {
1399    for (j, v) in out.iter_mut().enumerate() {
1400        let order = nu + T::from_f64(j as f64);
1401        if order < T::zero() {
1402            let int_order = order.abs().to_i64().unwrap_or(0);
1403            *v = *v * integer_sign::<T>(int_order);
1404        }
1405    }
1406}
1407
1408/// `n = 0` would underflow the `n - 1` order arithmetic of the helpers above.
1409#[inline]
1410fn check_nonempty<T>(out: &[T]) -> Result<(), Error> {
1411    if out.is_empty() {
1412        Err(Error::InvalidInput)
1413    } else {
1414        Ok(())
1415    }
1416}
1417
1418/// K with ν < 0: K_{−ν} = K_ν (even function).
1419fn besselk_seq_neg<T: BesselFloat>(
1420    nu: T,
1421    z: Complex<T>,
1422    scaling: Scaling,
1423    out: &mut [Complex<T>],
1424) -> Result<SeqInfo, Error> {
1425    check_nonempty(out)?;
1426    let nc = neg_count(nu, out.len());
1427    let f = &mut |start, buf: &mut [Complex<T>]| besk::zbesk(z, start, scaling, buf);
1428    let status = if nc == out.len() || as_integer(nu.abs()).is_some() {
1429        fill_abs_orders(out, nu, nc, f)?
1430    } else {
1431        fill_non_int_crossing(out, nu, nc, f)?
1432    };
1433    Ok(SeqInfo {
1434        underflow_count: recount_underflow(out),
1435        status,
1436    })
1437}
1438
1439/// H with ν < 0: H^(1)_{−ν} = exp(νπi)·H^(1)_ν, H^(2)_{−ν} = exp(−νπi)·H^(2)_ν.
1440fn hankel_seq_neg<T: BesselFloat>(
1441    kind: HankelKind,
1442    nu: T,
1443    z: Complex<T>,
1444    scaling: Scaling,
1445    out: &mut [Complex<T>],
1446) -> Result<SeqInfo, Error> {
1447    check_nonempty(out)?;
1448    let abs_nu = nu.abs();
1449    let nc = neg_count(nu, out.len());
1450    let f = &mut |start, buf: &mut [Complex<T>]| besh::zbesh(z, start, kind, scaling, buf);
1451    let status = if nc == out.len() || as_integer(abs_nu).is_some() {
1452        fill_abs_orders(out, nu, nc, f)?
1453    } else {
1454        fill_non_int_crossing(out, nu, nc, f)?
1455    };
1456    // Rotate the negative orders; |ν+j| = |ν| - j exactly
1457    for (j, v) in out.iter_mut().take(nc).enumerate() {
1458        let order = abs_nu - T::from_f64(j as f64);
1459        *v = reflect_h_element(order, kind, *v);
1460    }
1461    Ok(SeqInfo {
1462        underflow_count: recount_underflow(out),
1463        status,
1464    })
1465}
1466
1467/// J with ν < 0: J_{−ν} = cos(νπ)·J_ν − sin(νπ)·Y_ν, J_{−n} = (−1)^n·J_n.
1468fn besselj_seq_neg<T: BesselFloat>(
1469    nu: T,
1470    z: Complex<T>,
1471    scaling: Scaling,
1472    out: &mut [Complex<T>],
1473    ws: Workspace<'_, T>,
1474) -> Result<SeqInfo, Error> {
1475    check_nonempty(out)?;
1476    let n = out.len();
1477    let nc = neg_count(nu, n);
1478    let status = if as_integer(nu.abs()).is_some() {
1479        let f = &mut |start, buf: &mut [Complex<T>]| besj::zbesj(z, start, scaling, buf);
1480        let status = fill_abs_orders(out, nu, nc, f)?;
1481        apply_integer_sign(out, nu);
1482        status
1483    } else if utils::cospi(nu.abs()) == T::zero() {
1484        // Half-integer |ν|: cos(νπ) = 0, so the negative orders need only Y
1485        // (into `out`, its K values into the workspace)
1486        with_workspace(ws, n, |ws| {
1487            let abs_nu = nu.abs();
1488            let czero = Complex::new(T::zero(), T::zero());
1489            let (neg, pos) = out.split_at_mut(nc);
1490            let status1 = fill_abs_orders(neg, nu, nc, &mut |start, buf| {
1491                besy::zbesy_with(z, start, scaling, buf, Workspace::Slice(&mut *ws))
1492            })?;
1493            for (j, v) in neg.iter_mut().enumerate() {
1494                *v = reflect_j_element(abs_nu - T::from_f64(j as f64), czero, *v);
1495            }
1496            if pos.is_empty() {
1497                return Ok(status1);
1498            }
1499            let (_, status2) = besj::zbesj(z, nu + T::from_f64(nc as f64), scaling, pos)?;
1500            Ok(worse_status(status1, status2))
1501        })?
1502    } else {
1503        // J into `out`, Y into one half of the workspace and Y's K values
1504        // into the other (J first, as before)
1505        with_workspace(ws, 2 * n, |ws| {
1506            let (neg, pos) = out.split_at_mut(nc);
1507            let (y_buf, k_buf) = ws.split_at_mut(ws.len() / 2);
1508            let status1 = reflect_lattice(
1509                neg,
1510                nu.abs(),
1511                false,
1512                &mut |start, buf| besj::zbesj(z, start, scaling, buf),
1513                &mut |start, buf| {
1514                    besy::zbesy_with(z, start, scaling, buf, Workspace::Slice(&mut *k_buf))
1515                },
1516                &|order, j, y| reflect_j_element(order, j, y),
1517                y_buf,
1518            )?;
1519            if pos.is_empty() {
1520                return Ok(status1);
1521            }
1522            let (_, status2) = besj::zbesj(z, nu + T::from_f64(nc as f64), scaling, pos)?;
1523            Ok(worse_status(status1, status2))
1524        })?
1525    };
1526    Ok(SeqInfo {
1527        underflow_count: recount_underflow(out),
1528        status,
1529    })
1530}
1531
1532/// Y with ν < 0: Y_{−ν} = sin(νπ)·J_ν + cos(νπ)·Y_ν, Y_{−n} = (−1)^n·Y_n.
1533fn bessely_seq_neg<T: BesselFloat>(
1534    nu: T,
1535    z: Complex<T>,
1536    scaling: Scaling,
1537    out: &mut [Complex<T>],
1538    ws: Workspace<'_, T>,
1539) -> Result<SeqInfo, Error> {
1540    check_nonempty(out)?;
1541    let n = out.len();
1542    let nc = neg_count(nu, n);
1543    let is_int = as_integer(nu.abs()).is_some();
1544    // Half-integer |ν|: cos(νπ) = 0, so the negative orders need only J
1545    let half_int = !is_int && utils::cospi(nu.abs()) == T::zero();
1546    // Heap: Y's K values only, or also the Y values of the reflection formula
1547    let ws_len = if is_int || half_int { n } else { 2 * n };
1548    let status = with_workspace(ws, ws_len, |ws| {
1549        if is_int {
1550            let f = &mut |start, buf: &mut [Complex<T>]| {
1551                besy::zbesy_with(z, start, scaling, buf, Workspace::Slice(&mut *ws))
1552            };
1553            let status = fill_abs_orders(out, nu, nc, f)?;
1554            apply_integer_sign(out, nu);
1555            return Ok(status);
1556        }
1557        let (neg, pos) = out.split_at_mut(nc);
1558        let status1 = if half_int {
1559            let abs_nu = nu.abs();
1560            let czero = Complex::new(T::zero(), T::zero());
1561            let status = fill_abs_orders(neg, nu, nc, &mut |start, buf| {
1562                besj::zbesj(z, start, scaling, buf)
1563            })?;
1564            for (j, v) in neg.iter_mut().enumerate() {
1565                *v = reflect_y_element(abs_nu - T::from_f64(j as f64), *v, czero);
1566            }
1567            status
1568        } else {
1569            // J into `out`, Y into one half of the workspace and Y's K values
1570            // into the other (J first, as before)
1571            let (y_buf, k_buf) = ws.split_at_mut(ws.len() / 2);
1572            reflect_lattice(
1573                neg,
1574                nu.abs(),
1575                false,
1576                &mut |start, buf| besj::zbesj(z, start, scaling, buf),
1577                &mut |start, buf| {
1578                    besy::zbesy_with(z, start, scaling, buf, Workspace::Slice(&mut *k_buf))
1579                },
1580                &|order, j, y| reflect_y_element(order, j, y),
1581                y_buf,
1582            )?
1583        };
1584        if pos.is_empty() {
1585            return Ok(status1);
1586        }
1587        let pos_start = nu + T::from_f64(nc as f64);
1588        let (_, status2) = besy::zbesy_with(z, pos_start, scaling, pos, Workspace::Slice(ws))?;
1589        Ok(worse_status(status1, status2))
1590    })?;
1591    Ok(SeqInfo {
1592        underflow_count: recount_underflow(out),
1593        status,
1594    })
1595}
1596
1597/// I with ν < 0: I_{−ν} = I_ν + (2/π)·sin(νπ)·K_ν, I_{−n} = I_n.
1598fn besseli_seq_neg<T: BesselFloat>(
1599    nu: T,
1600    z: Complex<T>,
1601    scaling: Scaling,
1602    out: &mut [Complex<T>],
1603    ws: Workspace<'_, T>,
1604) -> Result<SeqInfo, Error> {
1605    check_nonempty(out)?;
1606    let n = out.len();
1607    let nc = neg_count(nu, n);
1608    let i_fn = &mut |start, buf: &mut [Complex<T>]| besi::zbesi(z, start, scaling, buf);
1609    let status = if as_integer(nu.abs()).is_some() {
1610        fill_abs_orders(out, nu, nc, i_fn)?
1611    } else {
1612        // K into `out` in one call, I into the workspace: Amos's K overflow
1613        // test uses the last order and length of the call, so splitting K
1614        // could fail where the whole call succeeds, while I's tests only get
1615        // more lenient in later chunks. The first I chunk goes first to keep
1616        // the I-then-K order.
1617        with_workspace(ws, n, |ws| {
1618            let (neg, pos) = out.split_at_mut(nc);
1619            let status1 = reflect_lattice(
1620                neg,
1621                nu.abs(),
1622                true,
1623                &mut |start, buf| besk::zbesk(z, start, scaling, buf),
1624                i_fn,
1625                &|order, k, i| {
1626                    let k = if scaling == Scaling::Exponential {
1627                        k_to_i_scaling_correction(z, k)
1628                    } else {
1629                        k
1630                    };
1631                    reflect_i_element(order, i, k)
1632                },
1633                ws,
1634            )?;
1635            if pos.is_empty() {
1636                return Ok(status1);
1637            }
1638            let (_, status2) = besi::zbesi(z, nu + T::from_f64(nc as f64), scaling, pos)?;
1639            Ok(worse_status(status1, status2))
1640        })?
1641    };
1642    Ok(SeqInfo {
1643        underflow_count: recount_underflow(out),
1644        status,
1645    })
1646}
1647
1648// ── Shared by `_seq_into` (stack workspace) and `_seq` (heap workspace) ──
1649
1650fn besselj_seq_impl<T: BesselFloat>(
1651    nu: T,
1652    z: Complex<T>,
1653    scaling: Scaling,
1654    out: &mut [Complex<T>],
1655    ws: Workspace<'_, T>,
1656) -> Result<SeqInfo, Error> {
1657    let info = if nu < T::zero() {
1658        besselj_seq_neg(nu, z, scaling, out, ws)?
1659    } else {
1660        let (underflow_count, status) = besj::zbesj(z, nu, scaling, out)?;
1661        SeqInfo {
1662            underflow_count,
1663            status,
1664        }
1665    };
1666    for v in out.iter_mut() {
1667        *v = axis::real_on_positive_axis(z, *v);
1668    }
1669    Ok(info)
1670}
1671
1672fn bessely_seq_impl<T: BesselFloat>(
1673    nu: T,
1674    z: Complex<T>,
1675    scaling: Scaling,
1676    out: &mut [Complex<T>],
1677    ws: Workspace<'_, T>,
1678) -> Result<SeqInfo, Error> {
1679    let info = if nu < T::zero() {
1680        bessely_seq_neg(nu, z, scaling, out, ws)?
1681    } else {
1682        let (underflow_count, status) = besy::zbesy_with(z, nu, scaling, out, ws)?;
1683        SeqInfo {
1684            underflow_count,
1685            status,
1686        }
1687    };
1688    for v in out.iter_mut() {
1689        *v = axis::real_on_positive_axis(z, *v);
1690    }
1691    axis::fix_seq(axis::AxisFn::Y, nu, z, scaling, out);
1692    Ok(info)
1693}
1694
1695fn besseli_seq_impl<T: BesselFloat>(
1696    nu: T,
1697    z: Complex<T>,
1698    scaling: Scaling,
1699    out: &mut [Complex<T>],
1700    ws: Workspace<'_, T>,
1701) -> Result<SeqInfo, Error> {
1702    if nu < T::zero() {
1703        return besseli_seq_neg(nu, z, scaling, out, ws);
1704    }
1705    let (underflow_count, status) = besi::zbesi(z, nu, scaling, out)?;
1706    Ok(SeqInfo {
1707        underflow_count,
1708        status,
1709    })
1710}
1711
1712fn hankel_seq_impl<T: BesselFloat>(
1713    kind: HankelKind,
1714    nu: T,
1715    z: Complex<T>,
1716    scaling: Scaling,
1717    out: &mut [Complex<T>],
1718) -> Result<SeqInfo, Error> {
1719    let info = if nu < T::zero() {
1720        hankel_seq_neg(kind, nu, z, scaling, out)?
1721    } else {
1722        let (underflow_count, status) = besh::zbesh(z, nu, kind, scaling, out)?;
1723        SeqInfo {
1724            underflow_count,
1725            status,
1726        }
1727    };
1728    axis::fix_seq(axis::AxisFn::Hankel(kind), nu, z, scaling, out);
1729    Ok(info)
1730}
1731
1732/// Compute J<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1733///
1734/// Like [`besselj_seq`], but writes into a caller-provided slice instead of
1735/// allocating, so it also works without an allocator (`no_std`). Returns a
1736/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1737/// returned, the contents of `out` are unspecified.
1738///
1739/// For negative non-integer ν, the Y values of the reflection formula need
1740/// workspace. It is a 1 KB stack buffer (f64), so long sequences are computed
1741/// in chunks. That is somewhat slower than [`besselj_seq`], and the last digits
1742/// can differ (within the accuracy of the single-value functions); close to
1743/// the overflow limit, one of the two can even return an error where the
1744/// other succeeds.
1745///
1746/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1747/// see [crate-level docs](crate#exponential-scaling) for details.
1748///
1749/// Supports negative ν via the same reflection formula as [`besselj`].
1750///
1751/// # Example
1752///
1753/// ```
1754/// use complex_bessel::*;
1755/// use num_complex::Complex;
1756///
1757/// let z = Complex::new(1.0_f64, 0.0);
1758///
1759/// // J_0(z), J_1(z), J_2(z) in one call
1760/// let mut out = [Complex::new(0.0, 0.0); 3];
1761/// let info = besselj_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1762/// assert!((out[0].re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
1763/// assert_eq!(info.status, Accuracy::Normal);
1764/// ```
1765///
1766/// # Errors
1767///
1768/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
1769/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1770/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1771/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1772pub fn besselj_seq_into<T: BesselFloat>(
1773    nu: T,
1774    z: Complex<T>,
1775    scaling: Scaling,
1776    out: &mut [Complex<T>],
1777) -> Result<SeqInfo, Error> {
1778    besselj_seq_impl(nu, z, scaling, out, Workspace::Stack)
1779}
1780
1781/// Compute Y<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1782///
1783/// Like [`bessely_seq`], but writes into a caller-provided slice instead of
1784/// allocating, so it also works without an allocator (`no_std`). Returns a
1785/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1786/// returned, the contents of `out` are unspecified.
1787///
1788/// The K values that Y is computed from (and, for negative non-integer ν, the
1789/// second function of the reflection formula) need workspace. It is a 1 KB
1790/// stack buffer (f64), so long sequences are computed in chunks. That is
1791/// somewhat slower than [`bessely_seq`], and the last digits can differ
1792/// (within the accuracy of the single-value functions); close to the overflow
1793/// limit, one of the two can even return an error where the other succeeds.
1794///
1795/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1796/// see [crate-level docs](crate#exponential-scaling) for details.
1797///
1798/// Supports negative ν via the same reflection formula as [`bessely`].
1799///
1800/// # Example
1801///
1802/// ```
1803/// use complex_bessel::*;
1804/// use num_complex::Complex;
1805///
1806/// let z = Complex::new(1.0_f64, 0.0);
1807///
1808/// // Y_0(z), Y_1(z), Y_2(z) in one call
1809/// let mut out = [Complex::new(0.0, 0.0); 3];
1810/// let info = bessely_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1811/// assert!((out[0].re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
1812/// assert_eq!(info.status, Accuracy::Normal);
1813/// ```
1814///
1815/// # Errors
1816///
1817/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0 (unless every order is a negative half-integer, where Y_ν(0) = 0).
1818/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1819/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1820/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1821pub fn bessely_seq_into<T: BesselFloat>(
1822    nu: T,
1823    z: Complex<T>,
1824    scaling: Scaling,
1825    out: &mut [Complex<T>],
1826) -> Result<SeqInfo, Error> {
1827    bessely_seq_impl(nu, z, scaling, out, Workspace::Stack)
1828}
1829
1830/// Compute I<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1831///
1832/// Like [`besseli_seq`], but writes into a caller-provided slice instead of
1833/// allocating, so it also works without an allocator (`no_std`). Returns a
1834/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1835/// returned, the contents of `out` are unspecified.
1836///
1837/// For negative non-integer ν, the I values of the reflection formula need
1838/// workspace. It is a 1 KB stack buffer (f64), so long sequences are computed
1839/// in chunks. That is somewhat slower than [`besseli_seq`], and the last digits
1840/// can differ (within the accuracy of the single-value functions); close to
1841/// the overflow limit, one of the two can even return an error where the
1842/// other succeeds.
1843///
1844/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1845/// see [crate-level docs](crate#exponential-scaling) for details.
1846///
1847/// Supports negative ν via the same reflection formula as [`besseli`].
1848///
1849/// # Example
1850///
1851/// ```
1852/// use complex_bessel::*;
1853/// use num_complex::Complex;
1854///
1855/// let z = Complex::new(1.0_f64, 0.0);
1856///
1857/// // I_0(z), I_1(z), I_2(z) in one call
1858/// let mut out = [Complex::new(0.0, 0.0); 3];
1859/// let info = besseli_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1860/// assert!((out[0].re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
1861/// assert_eq!(info.status, Accuracy::Normal);
1862/// ```
1863///
1864/// # Errors
1865///
1866/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
1867/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1868/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1869/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1870pub fn besseli_seq_into<T: BesselFloat>(
1871    nu: T,
1872    z: Complex<T>,
1873    scaling: Scaling,
1874    out: &mut [Complex<T>],
1875) -> Result<SeqInfo, Error> {
1876    besseli_seq_impl(nu, z, scaling, out, Workspace::Stack)
1877}
1878
1879/// Compute K<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1880///
1881/// Like [`besselk_seq`], but writes into a caller-provided slice instead of
1882/// allocating, so it also works without an allocator (`no_std`). Returns a
1883/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1884/// returned, the contents of `out` are unspecified.
1885///
1886/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1887/// see [crate-level docs](crate#exponential-scaling) for details.
1888///
1889/// Supports negative ν via the same reflection formula as [`besselk`].
1890///
1891/// # Example
1892///
1893/// ```
1894/// use complex_bessel::*;
1895/// use num_complex::Complex;
1896///
1897/// let z = Complex::new(1.0_f64, 0.0);
1898///
1899/// // K_0(z), K_1(z), K_2(z) in one call
1900/// let mut out = [Complex::new(0.0, 0.0); 3];
1901/// let info = besselk_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1902/// assert!((out[0].re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
1903/// assert_eq!(info.status, Accuracy::Normal);
1904/// ```
1905///
1906/// # Errors
1907///
1908/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0.
1909/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1910/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1911/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1912pub fn besselk_seq_into<T: BesselFloat>(
1913    nu: T,
1914    z: Complex<T>,
1915    scaling: Scaling,
1916    out: &mut [Complex<T>],
1917) -> Result<SeqInfo, Error> {
1918    let info = if nu < T::zero() {
1919        besselk_seq_neg(nu, z, scaling, out)?
1920    } else {
1921        let (underflow_count, status) = besk::zbesk(z, nu, scaling, out)?;
1922        SeqInfo {
1923            underflow_count,
1924            status,
1925        }
1926    };
1927    axis::fix_seq(axis::AxisFn::K, nu, z, scaling, out);
1928    Ok(info)
1929}
1930
1931/// Compute H<sub>ν+j</sub><sup>(1)</sup>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1932///
1933/// Like [`hankel1_seq`], but writes into a caller-provided slice instead of
1934/// allocating, so it also works without an allocator (`no_std`). Returns a
1935/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1936/// returned, the contents of `out` are unspecified.
1937///
1938/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1939/// see [crate-level docs](crate#exponential-scaling) for details.
1940///
1941/// Supports negative ν via the same reflection formula as [`hankel1`].
1942///
1943/// # Example
1944///
1945/// ```
1946/// use complex_bessel::*;
1947/// use num_complex::Complex;
1948///
1949/// let z = Complex::new(1.0_f64, 0.0);
1950///
1951/// // H^(1)_0(z), H^(1)_1(z) in one call
1952/// let mut out = [Complex::new(0.0, 0.0); 2];
1953/// let info = hankel1_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1954/// assert!((out[0].im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
1955/// assert_eq!(info.status, Accuracy::Normal);
1956/// ```
1957///
1958/// # Errors
1959///
1960/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0.
1961/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1962/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1963/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1964pub fn hankel1_seq_into<T: BesselFloat>(
1965    nu: T,
1966    z: Complex<T>,
1967    scaling: Scaling,
1968    out: &mut [Complex<T>],
1969) -> Result<SeqInfo, Error> {
1970    hankel_seq_impl(HankelKind::First, nu, z, scaling, out)
1971}
1972
1973/// Compute H<sub>ν+j</sub><sup>(2)</sup>(z) for j = 0, 1, …, n−1 into `out`, where n = `out.len()`.
1974///
1975/// Like [`hankel2_seq`], but writes into a caller-provided slice instead of
1976/// allocating, so it also works without an allocator (`no_std`). Returns a
1977/// [`SeqInfo`] with the underflow count and [`Accuracy`]. If an error is
1978/// returned, the contents of `out` are unspecified.
1979///
1980/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1981/// see [crate-level docs](crate#exponential-scaling) for details.
1982///
1983/// Supports negative ν via the same reflection formula as [`hankel2`].
1984///
1985/// # Example
1986///
1987/// ```
1988/// use complex_bessel::*;
1989/// use num_complex::Complex;
1990///
1991/// let z = Complex::new(1.0_f64, 0.0);
1992///
1993/// // H^(2)_0(z), H^(2)_1(z) in one call
1994/// let mut out = [Complex::new(0.0, 0.0); 2];
1995/// let info = hankel2_seq_into(0.0, z, Scaling::Unscaled, &mut out).unwrap();
1996/// assert!((out[0].im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
1997/// assert_eq!(info.status, Accuracy::Normal);
1998/// ```
1999///
2000/// # Errors
2001///
2002/// - [`Error::InvalidInput`] if `out` is empty, if z or ν is not finite (NaN or infinite), or if z = 0.
2003/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
2004/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2005/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2006pub fn hankel2_seq_into<T: BesselFloat>(
2007    nu: T,
2008    z: Complex<T>,
2009    scaling: Scaling,
2010    out: &mut [Complex<T>],
2011) -> Result<SeqInfo, Error> {
2012    hankel_seq_impl(HankelKind::Second, nu, z, scaling, out)
2013}
2014
2015// ── Allocating sequence functions ──
2016
2017#[cfg(feature = "std")]
2018fn seq_helper<T: BesselFloat>(
2019    n: usize,
2020    f: impl FnOnce(&mut [Complex<T>]) -> Result<SeqInfo, Error>,
2021) -> Result<BesselResult<T>, Error> {
2022    let zero = T::zero();
2023    let mut values = alloc::vec![Complex::new(zero, zero); n];
2024    let info = f(&mut values)?;
2025    Ok(BesselResult {
2026        values,
2027        underflow_count: info.underflow_count,
2028        status: info.status,
2029    })
2030}
2031
2032#[cfg(feature = "std")]
2033/// Compute J<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
2034///
2035/// Allocating version of [`besselj_seq_into`].
2036///
2037/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2038/// - [`Accuracy::Normal`] — no significant precision loss
2039/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2040///
2041/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2042/// see [crate-level docs](crate#exponential-scaling) for details.
2043///
2044/// Supports negative ν via the same reflection formula as [`besselj`].
2045///
2046/// # Example
2047///
2048/// ```
2049/// # #[cfg(feature = "std")] {
2050/// use complex_bessel::*;
2051/// use num_complex::Complex;
2052///
2053/// let z = Complex::new(1.0_f64, 0.0);
2054///
2055/// // J_0(z), J_1(z), J_2(z) in one call
2056/// let result = besselj_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
2057/// assert_eq!(result.values.len(), 3);
2058/// assert!((result.values[0].re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
2059/// # }
2060/// ```
2061///
2062/// # Errors
2063///
2064/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
2065/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
2066/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2067/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2068pub fn besselj_seq<T: BesselFloat>(
2069    nu: T,
2070    z: Complex<T>,
2071    n: usize,
2072    scaling: Scaling,
2073) -> Result<BesselResult<T>, Error> {
2074    seq_helper(n, |out| {
2075        besselj_seq_impl(nu, z, scaling, out, Workspace::Heap)
2076    })
2077}
2078
2079#[cfg(feature = "std")]
2080/// Compute Y<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
2081///
2082/// Allocating version of [`bessely_seq_into`].
2083///
2084/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2085/// - [`Accuracy::Normal`] — no significant precision loss
2086/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2087///
2088/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2089/// see [crate-level docs](crate#exponential-scaling) for details.
2090///
2091/// Supports negative ν via the same reflection formula as [`bessely`].
2092///
2093/// # Example
2094///
2095/// ```
2096/// # #[cfg(feature = "std")] {
2097/// use complex_bessel::*;
2098/// use num_complex::Complex;
2099///
2100/// let z = Complex::new(1.0_f64, 0.0);
2101///
2102/// // Y_0(z), Y_1(z), Y_2(z) in one call
2103/// let result = bessely_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
2104/// assert_eq!(result.values.len(), 3);
2105/// assert!((result.values[0].re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
2106/// # }
2107/// ```
2108///
2109/// # Errors
2110///
2111/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0 (unless every order is a negative half-integer, where Y_ν(0) = 0).
2112/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
2113/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2114/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2115pub fn bessely_seq<T: BesselFloat>(
2116    nu: T,
2117    z: Complex<T>,
2118    n: usize,
2119    scaling: Scaling,
2120) -> Result<BesselResult<T>, Error> {
2121    seq_helper(n, |out| {
2122        bessely_seq_impl(nu, z, scaling, out, Workspace::Heap)
2123    })
2124}
2125
2126#[cfg(feature = "std")]
2127/// Compute I<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
2128///
2129/// Allocating version of [`besseli_seq_into`].
2130///
2131/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2132/// - [`Accuracy::Normal`] — no significant precision loss
2133/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2134///
2135/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2136/// see [crate-level docs](crate#exponential-scaling) for details.
2137///
2138/// Supports negative ν via the same reflection formula as [`besseli`].
2139///
2140/// # Example
2141///
2142/// ```
2143/// # #[cfg(feature = "std")] {
2144/// use complex_bessel::*;
2145/// use num_complex::Complex;
2146///
2147/// let z = Complex::new(1.0_f64, 0.0);
2148///
2149/// // I_0(z), I_1(z), I_2(z) in one call
2150/// let result = besseli_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
2151/// assert_eq!(result.values.len(), 3);
2152/// assert!((result.values[0].re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
2153/// # }
2154/// ```
2155///
2156/// # Errors
2157///
2158/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
2159/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
2160/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2161/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2162pub fn besseli_seq<T: BesselFloat>(
2163    nu: T,
2164    z: Complex<T>,
2165    n: usize,
2166    scaling: Scaling,
2167) -> Result<BesselResult<T>, Error> {
2168    seq_helper(n, |out| {
2169        besseli_seq_impl(nu, z, scaling, out, Workspace::Heap)
2170    })
2171}
2172
2173#[cfg(feature = "std")]
2174/// Compute K<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
2175///
2176/// Allocating version of [`besselk_seq_into`].
2177///
2178/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2179/// - [`Accuracy::Normal`] — no significant precision loss
2180/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2181///
2182/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2183/// see [crate-level docs](crate#exponential-scaling) for details.
2184///
2185/// Supports negative ν via the same reflection formula as [`besselk`].
2186///
2187/// # Example
2188///
2189/// ```
2190/// # #[cfg(feature = "std")] {
2191/// use complex_bessel::*;
2192/// use num_complex::Complex;
2193///
2194/// let z = Complex::new(1.0_f64, 0.0);
2195///
2196/// // K_0(z), K_1(z), K_2(z) in one call
2197/// let result = besselk_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
2198/// assert_eq!(result.values.len(), 3);
2199/// assert!((result.values[0].re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
2200/// # }
2201/// ```
2202///
2203/// # Errors
2204///
2205/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
2206/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
2207/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2208/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2209pub fn besselk_seq<T: BesselFloat>(
2210    nu: T,
2211    z: Complex<T>,
2212    n: usize,
2213    scaling: Scaling,
2214) -> Result<BesselResult<T>, Error> {
2215    seq_helper(n, |out| besselk_seq_into(nu, z, scaling, out))
2216}
2217
2218#[cfg(feature = "std")]
2219/// Compute H<sub>ν+j</sub><sup>(1)</sup>(z) for j = 0, 1, …, n−1 in a single call.
2220///
2221/// Allocating version of [`hankel1_seq_into`].
2222///
2223/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2224/// - [`Accuracy::Normal`] — no significant precision loss
2225/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2226///
2227/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2228/// see [crate-level docs](crate#exponential-scaling) for details.
2229///
2230/// Supports negative ν via the same reflection formula as [`hankel1`].
2231///
2232/// # Example
2233///
2234/// ```
2235/// # #[cfg(feature = "std")] {
2236/// use complex_bessel::*;
2237/// use num_complex::Complex;
2238///
2239/// let z = Complex::new(1.0_f64, 0.0);
2240///
2241/// // H^(1)_0(z), H^(1)_1(z) in one call
2242/// let result = hankel1_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
2243/// assert_eq!(result.values.len(), 2);
2244/// assert!((result.values[0].im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
2245/// # }
2246/// ```
2247///
2248/// # Errors
2249///
2250/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
2251/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
2252/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2253/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2254pub fn hankel1_seq<T: BesselFloat>(
2255    nu: T,
2256    z: Complex<T>,
2257    n: usize,
2258    scaling: Scaling,
2259) -> Result<BesselResult<T>, Error> {
2260    seq_helper(n, |out| hankel1_seq_into(nu, z, scaling, out))
2261}
2262
2263#[cfg(feature = "std")]
2264/// Compute H<sub>ν+j</sub><sup>(2)</sup>(z) for j = 0, 1, …, n−1 in a single call.
2265///
2266/// Allocating version of [`hankel2_seq_into`].
2267///
2268/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
2269/// - [`Accuracy::Normal`] — no significant precision loss
2270/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
2271///
2272/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
2273/// see [crate-level docs](crate#exponential-scaling) for details.
2274///
2275/// Supports negative ν via the same reflection formula as [`hankel2`].
2276///
2277/// # Example
2278///
2279/// ```
2280/// # #[cfg(feature = "std")] {
2281/// use complex_bessel::*;
2282/// use num_complex::Complex;
2283///
2284/// let z = Complex::new(1.0_f64, 0.0);
2285///
2286/// // H^(2)_0(z), H^(2)_1(z) in one call
2287/// let result = hankel2_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
2288/// assert_eq!(result.values.len(), 2);
2289/// assert!((result.values[0].im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
2290/// # }
2291/// ```
2292///
2293/// # Errors
2294///
2295/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
2296/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
2297/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
2298/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
2299pub fn hankel2_seq<T: BesselFloat>(
2300    nu: T,
2301    z: Complex<T>,
2302    n: usize,
2303    scaling: Scaling,
2304) -> Result<BesselResult<T>, Error> {
2305    seq_helper(n, |out| hankel2_seq_into(nu, z, scaling, out))
2306}
2307
2308// ── Tests for non-finite input rejection at the public API level ──
2309// Deliberately gated on test only (not std), so they run in every
2310// feature configuration.
2311
2312#[cfg(test)]
2313mod non_finite_tests {
2314    use super::*;
2315    use num_complex::{Complex32, Complex64};
2316
2317    #[test]
2318    fn bessel_non_finite_z_returns_invalid_input() {
2319        let nan = f64::NAN;
2320        for z in [
2321            Complex64::new(nan, 0.0),
2322            Complex64::new(0.0, nan),
2323            Complex64::new(nan, nan),
2324            Complex64::new(f64::INFINITY, 0.0),
2325        ] {
2326            assert!(matches!(besselj(0.0, z), Err(Error::InvalidInput)));
2327            assert!(matches!(besselj_scaled(1.0, z), Err(Error::InvalidInput)));
2328            assert!(matches!(bessely(0.0, z), Err(Error::InvalidInput)));
2329            assert!(matches!(besseli(0.0, z), Err(Error::InvalidInput)));
2330            assert!(matches!(besselk(0.0, z), Err(Error::InvalidInput)));
2331            assert!(matches!(hankel1(0.0, z), Err(Error::InvalidInput)));
2332            assert!(matches!(hankel2(0.0, z), Err(Error::InvalidInput)));
2333            // Negative orders exercise the reflection paths in the wrappers
2334            // (one function for half-integer |ν|, two otherwise).
2335            assert!(matches!(besselj(-2.5, z), Err(Error::InvalidInput)));
2336            assert!(matches!(besselj(-2.3, z), Err(Error::InvalidInput)));
2337            assert!(matches!(besselk(-2.5, z), Err(Error::InvalidInput)));
2338        }
2339    }
2340
2341    #[test]
2342    fn bessel_non_finite_nu_returns_invalid_input() {
2343        let z = Complex64::new(1.0, 1.0);
2344        for nu in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
2345            assert!(matches!(besselj(nu, z), Err(Error::InvalidInput)));
2346            assert!(matches!(bessely(nu, z), Err(Error::InvalidInput)));
2347            assert!(matches!(besseli(nu, z), Err(Error::InvalidInput)));
2348            assert!(matches!(besselk(nu, z), Err(Error::InvalidInput)));
2349            assert!(matches!(hankel1(nu, z), Err(Error::InvalidInput)));
2350            assert!(matches!(hankel2(nu, z), Err(Error::InvalidInput)));
2351        }
2352    }
2353
2354    #[test]
2355    fn bessel_non_finite_f32_returns_invalid_input() {
2356        let z = Complex32::new(1.0, 1.0);
2357        assert!(matches!(
2358            besselj(0.0f32, Complex32::new(f32::NAN, 0.0)),
2359            Err(Error::InvalidInput)
2360        ));
2361        assert!(matches!(besselj(f32::NAN, z), Err(Error::InvalidInput)));
2362    }
2363
2364    #[test]
2365    fn airy_non_finite_z_returns_invalid_input() {
2366        let nan = f64::NAN;
2367        for z in [
2368            Complex64::new(nan, 0.0),
2369            Complex64::new(0.0, nan),
2370            Complex64::new(f64::INFINITY, 0.0),
2371        ] {
2372            assert!(matches!(airy(z), Err(Error::InvalidInput)));
2373            assert!(matches!(airyprime(z), Err(Error::InvalidInput)));
2374            assert!(matches!(biry(z), Err(Error::InvalidInput)));
2375            assert!(matches!(biryprime(z), Err(Error::InvalidInput)));
2376            assert!(matches!(
2377                airy_raw(z, Scaling::Unscaled),
2378                Err(Error::InvalidInput)
2379            ));
2380            assert!(matches!(
2381                biry_raw(z, Scaling::Unscaled),
2382                Err(Error::InvalidInput)
2383            ));
2384        }
2385    }
2386
2387    #[test]
2388    fn finite_overflowing_modulus_keeps_total_precision_loss() {
2389        // |z| overflows to infinity while both components are finite: this
2390        // must stay TotalPrecisionLoss (range check), not InvalidInput.
2391        let z = Complex64::new(1.7e308, 1.7e308);
2392        assert!(matches!(besselj(0.0, z), Err(Error::TotalPrecisionLoss)));
2393    }
2394
2395    #[test]
2396    fn seq_non_finite_inputs_return_invalid_input() {
2397        let z_nan = Complex64::new(f64::NAN, 0.0);
2398        let z = Complex64::new(1.0, 1.0);
2399        let mut out = [Complex64::new(0.0, 0.0); 3];
2400        assert!(matches!(
2401            besselj_seq_into(f64::NAN, z, Scaling::Unscaled, &mut out[..2]),
2402            Err(Error::InvalidInput)
2403        ));
2404        assert!(matches!(
2405            besselj_seq_into(0.0, z_nan, Scaling::Unscaled, &mut out[..2]),
2406            Err(Error::InvalidInput)
2407        ));
2408        assert!(matches!(
2409            besselk_seq_into(-2.5, z_nan, Scaling::Unscaled, &mut out),
2410            Err(Error::InvalidInput)
2411        ));
2412    }
2413}
2414
2415// ── Scaled vs unscaled consistency ──
2416//
2417// Every `_scaled` function is the unscaled one times an elementary factor, so
2418// the two must agree wherever both are representable. The grid reaches the
2419// regions where the scaled paths switch to underflow/overflow handling
2420// (2|Im z| above ELIM for Y, large orders), which a value comparison at
2421// moderate arguments does not.
2422
2423#[cfg(test)]
2424mod scaling_tests {
2425    use super::*;
2426    use num_complex::Complex64;
2427
2428    pub(super) type F<T> = fn(T, Complex<T>) -> Result<Complex<T>, Error>;
2429
2430    pub(super) fn fns<T: BesselFloat>() -> [(&'static str, F<T>, F<T>); 6] {
2431        [
2432            ("J", besselj, besselj_scaled),
2433            ("Y", bessely, bessely_scaled),
2434            ("I", besseli, besseli_scaled),
2435            ("K", besselk, besselk_scaled),
2436            ("H1", hankel1, hankel1_scaled),
2437            ("H2", hankel2, hankel2_scaled),
2438        ]
2439    }
2440
2441    /// Log of the factor in scaled = exp(c)·unscaled.
2442    fn log_factor(name: &str, z: Complex64) -> Complex64 {
2443        let i = Complex64::new(0.0, 1.0);
2444        match name {
2445            "J" | "Y" => Complex64::new(-z.im.abs(), 0.0),
2446            "I" => Complex64::new(-z.re.abs(), 0.0),
2447            "K" => z,
2448            "H1" => -i * z,
2449            _ => i * z,
2450        }
2451    }
2452
2453    /// Compare scaled with exp(c)·unscaled, formed in log space so neither
2454    /// the factor nor the product overflows. Pairs where either value fails,
2455    /// is non-finite, or lies below `floor` (underflow handling) are skipped.
2456    fn check<T: BesselFloat>(nus: &[f64], radii: &[f64], floor: f64, tol: f64) {
2457        let to64 = |v: Complex<T>| Complex64::new(v.re.to_f64().unwrap(), v.im.to_f64().unwrap());
2458        let mut compared = 0;
2459        for (name, unscaled, scaled) in fns::<T>() {
2460            for &nu in nus {
2461                for &r in radii {
2462                    for k in 0..12 {
2463                        // 12 angles over the full circle, including both axes
2464                        let th =
2465                            -core::f64::consts::PI + core::f64::consts::FRAC_PI_6 * f64::from(k);
2466                        let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
2467                        let nu_t = T::from_f64(nu);
2468                        let (Ok(u), Ok(s)) = (unscaled(nu_t, z), scaled(nu_t, z)) else {
2469                            continue;
2470                        };
2471                        let (u, s) = (to64(u), to64(s));
2472                        let ok = |v: Complex64| v.norm().is_finite() && v.norm() > floor;
2473                        if !ok(u) || !ok(s) {
2474                            continue;
2475                        }
2476                        let expected = (u.ln() + log_factor(name, to64(z))).exp();
2477                        let err = (s - expected).norm() / expected.norm();
2478                        assert!(
2479                            err < tol,
2480                            "{name}: nu={nu}, z={z:?}: scaled={s:?}, exp(c)*unscaled={expected:?}, \
2481                             rel_err={err:e}"
2482                        );
2483                        compared += 1;
2484                    }
2485                }
2486            }
2487        }
2488        assert!(compared > 0);
2489    }
2490
2491    #[test]
2492    fn scaled_matches_unscaled_f64() {
2493        // Radii past ELIM/2 ≈ 350 with orders up to beyond |z|. Observed up to
2494        // ~6e-13 (large ν and |z|); a lost or mis-scaled term gives O(1).
2495        check::<f64>(
2496            &[0.0, 0.3, 2.5, 45.7, 250.0, 575.0, 800.0, 1200.0],
2497            &[0.5, 5.0, 40.0, 200.0, 360.0, 450.0, 600.0],
2498            1e-290,
2499            1e-10,
2500        );
2501    }
2502
2503    #[test]
2504    fn scaled_matches_unscaled_f32() {
2505        // ELIM/2 ≈ 40 in f32. Observed up to ~4e-5.
2506        check::<f32>(
2507            &[0.0, 0.3, 2.5, 25.7, 65.0, 90.0, 130.0],
2508            &[0.5, 5.0, 20.0, 41.0, 50.0, 60.0, 75.0],
2509            1e-30,
2510            1e-3,
2511        );
2512    }
2513}
2514
2515#[cfg(test)]
2516mod special_value_tests {
2517    use super::scaling_tests::fns;
2518    use super::*;
2519
2520    type AiryFn<T> = fn(Complex<T>) -> Result<Complex<T>, Error>;
2521    type AiryRawFn<T> = fn(Complex<T>, Scaling) -> Result<AiryResult<T>, Error>;
2522
2523    /// As in Amos, a real or imaginary part of −0 must give the same result
2524    /// as +0: on the negative real axis the functions with a branch cut take
2525    /// their values from above it, and the Airy functions are entire.
2526    fn check_signed_zero<T: BesselFloat>() {
2527        let airy_fns: [(&str, AiryFn<T>); 8] = [
2528            ("Ai", airy),
2529            ("Ai'", airyprime),
2530            ("Bi", biry),
2531            ("Bi'", biryprime),
2532            ("Ai scaled", airy_scaled),
2533            ("Ai' scaled", airyprime_scaled),
2534            ("Bi scaled", biry_scaled),
2535            ("Bi' scaled", biryprime_scaled),
2536        ];
2537        let raw_fns: [(&str, AiryRawFn<T>); 4] = [
2538            ("Ai raw", airy_raw),
2539            ("Ai' raw", airyprime_raw),
2540            ("Bi raw", biry_raw),
2541            ("Bi' raw", biryprime_raw),
2542        ];
2543        let zero = T::zero();
2544        // Power series (|z| <= 1) and the K/I paths, up to the overflow limits
2545        for x in [0.5, 2.0, 5.0, 50.0, 105.0] {
2546            let x = T::from_f64(x);
2547            for (plus, minus) in [
2548                (Complex::new(-x, zero), Complex::new(-x, -zero)),
2549                (Complex::new(x, zero), Complex::new(x, -zero)),
2550                (Complex::new(zero, x), Complex::new(-zero, x)),
2551                (Complex::new(zero, -x), Complex::new(-zero, -x)),
2552            ] {
2553                for (name, f) in airy_fns {
2554                    assert_eq!(f(plus), f(minus), "{name}: {plus:?} vs {minus:?}");
2555                }
2556                for (name, f) in raw_fns {
2557                    for scaling in [Scaling::Unscaled, Scaling::Exponential] {
2558                        assert_eq!(
2559                            f(plus, scaling),
2560                            f(minus, scaling),
2561                            "{name} {scaling:?}: {plus:?} vs {minus:?}"
2562                        );
2563                    }
2564                }
2565                for (name, unscaled, scaled) in fns::<T>() {
2566                    for nu in [0.0, 0.3, 1.0, 2.5, -0.3, -2.5, 90.5] {
2567                        let nu = T::from_f64(nu);
2568                        for f in [unscaled, scaled] {
2569                            assert_eq!(
2570                                f(nu, plus),
2571                                f(nu, minus),
2572                                "{name}: nu={nu:?}, {plus:?} vs {minus:?}"
2573                            );
2574                        }
2575                    }
2576                }
2577            }
2578        }
2579    }
2580
2581    #[test]
2582    fn signed_zero_matches_positive_zero_f64() {
2583        check_signed_zero::<f64>();
2584    }
2585
2586    #[test]
2587    fn signed_zero_matches_positive_zero_f32() {
2588        check_signed_zero::<f32>();
2589    }
2590
2591    /// For half-integer |ν|, cos(νπ) = 0, so J_{−ν} = −sin(νπ)·Y_ν and
2592    /// Y_{−ν} = sin(νπ)·J_ν: the result (or error) is that of the one
2593    /// remaining function, even where the vanishing one overflows (Y_ν for
2594    /// large ν and small |z|) or is undefined (Y_ν at z = 0).
2595    fn check_negative_half_integer<T: BesselFloat>(orders: &[f64]) {
2596        let zero = T::zero();
2597        let zs = [
2598            Complex::new(T::one(), zero),
2599            Complex::new(T::from_f64(100.0), zero),
2600            Complex::from_polar(T::from_f64(0.3), T::one()),
2601            Complex::from_polar(T::from_f64(5.0), T::from_f64(2.0)),
2602            Complex::new(zero, zero),
2603        ];
2604        let pairs: [(&str, scaling_tests::F<T>, scaling_tests::F<T>); 2] = [
2605            ("unscaled", besselj, bessely),
2606            ("scaled", besselj_scaled, bessely_scaled),
2607        ];
2608        for &a in orders {
2609            let a = T::from_f64(a);
2610            let s = utils::sinpi(a);
2611            for (name, j, y) in pairs {
2612                for z in zs {
2613                    assert_eq!(
2614                        y(-a, z),
2615                        j(a, z).map(|v| v * s),
2616                        "Y {name}: nu={:?}, z={z:?}",
2617                        -a
2618                    );
2619                    assert_eq!(
2620                        j(-a, z),
2621                        y(a, z).map(|v| -(v * s)),
2622                        "J {name}: nu={:?}, z={z:?}",
2623                        -a
2624                    );
2625                }
2626            }
2627        }
2628    }
2629
2630    #[test]
2631    fn negative_half_integer_needs_one_function_f64() {
2632        check_negative_half_integer::<f64>(&[0.5, 2.5, 10.5, 150.5, 200.5, 519.5]);
2633    }
2634
2635    #[test]
2636    fn negative_half_integer_needs_one_function_f32() {
2637        check_negative_half_integer::<f32>(&[0.5, 2.5, 10.5, 30.5, 40.5, 150.5]);
2638    }
2639
2640    /// Above FNUL, the uniform expansions (ZUNI2, ZUNK2) evaluate the Airy
2641    /// function at about (1.5|z|)^(2/3), beyond the Airy functions' own range
2642    /// limit once |z| exceeds about 7.2e8. Amos leaves the result undefined
2643    /// there; these values were 0 (or, for Y, missing the K part). The
2644    /// Wronskians (DLMF 10.5.2, 10.5.5, 10.28.2) rule out a zero or a missing term.
2645    /// They cannot see a phase error common to ν and ν + 1; against mpmath
2646    /// the error here is up to ~3e-5, as at smaller |z| (status `Reduced`).
2647    #[test]
2648    fn uniform_expansion_beyond_airy_range_f64() {
2649        use core::f64::consts::{FRAC_PI_2, PI};
2650        let czero = Complex::new(0.0, 0.0);
2651        let check =
2652            |what: &str, nu: f64, z: Complex<f64>, w: Complex<f64>, expected: Complex<f64>| {
2653                let err = (w - expected).norm() / expected.norm();
2654                assert!(err < 1e-5, "{what}: nu={nu}, z={z:?}, rel_err={err:e}");
2655            };
2656        // Within ~30° of the real axis (H, J, Y) or the imaginary axis (I, K),
2657        // where ZUNI2 and ZUNK2 are used
2658        let args = [
2659            0.0,
2660            0.3,
2661            FRAC_PI_2 - 0.3,
2662            FRAC_PI_2,
2663            FRAC_PI_2 + 0.3,
2664            PI - 0.3,
2665            PI,
2666            -0.3,
2667            -FRAC_PI_2,
2668            -FRAC_PI_2 + 0.3,
2669        ];
2670        // ν = 1e5 also takes J and I through ZUNI2 (2|z| < ν²)
2671        for nu in [86.0, 90.5, 1000.0, 1e5] {
2672            for r in [7.5e8, 9e8, 1.05e9] {
2673                for th in args {
2674                    let z = Complex::from_polar(r, th);
2675                    // H1·H2 (the scaling factors cancel), from single values and from one call
2676                    let h1 = |n: f64| hankel1_scaled(n, z).unwrap();
2677                    let h2 = |n: f64| hankel2_scaled(n, z).unwrap();
2678                    let expected = Complex::new(0.0, 4.0 / PI) / z;
2679                    let w = h1(nu) * h2(nu + 1.0) - h1(nu + 1.0) * h2(nu);
2680                    check("H single", nu, z, w, expected);
2681                    let (mut s1, mut s2) = ([czero; 2], [czero; 2]);
2682                    hankel1_seq_into(nu, z, Scaling::Exponential, &mut s1).unwrap();
2683                    hankel2_seq_into(nu, z, Scaling::Exponential, &mut s2).unwrap();
2684                    check("H seq", nu, z, s1[0] * s2[1] - s1[1] * s2[0], expected);
2685                    // I·K for Re z ≥ 0, where the scaling factors leave exp(i Im z)
2686                    if z.re >= 0.0 {
2687                        let i = |n: f64| besseli_scaled(n, z).unwrap();
2688                        let k = |n: f64| besselk_scaled(n, z).unwrap();
2689                        let w = i(nu) * k(nu + 1.0) + i(nu + 1.0) * k(nu);
2690                        let expected = Complex::from_polar(1.0, z.im) / z;
2691                        check("I K", nu, z, w, expected);
2692                    }
2693                }
2694                for x in [r, -r] {
2695                    let z = Complex::new(x, 0.0);
2696                    let j = |n: f64| besselj(n, z).unwrap();
2697                    let y = |n: f64| bessely(n, z).unwrap();
2698                    let w = j(nu) * y(nu + 1.0) - j(nu + 1.0) * y(nu);
2699                    check("J Y", nu, z, w, Complex::new(-2.0 / PI, 0.0) / z);
2700                }
2701            }
2702        }
2703    }
2704
2705    /// Scaled K and H continued across the imaginary axis (ZACON) combine
2706    /// K(−z), rescaled by exp(−2z), with I(−z). Fortran rounds the phase of
2707    /// that rescaling to an ulp of 2·Im z, a relative error of up to eps·|z|.
2708    /// Half-integer orders have closed forms (DLMF 10.49.12, 10.16.2, 10.39.1);
2709    /// the points are where the K term is not negligible (near the imaginary
2710    /// axis for K and I, near the real axis for H). The K sequence (n = 5)
2711    /// reaches ZACON's second and loop calls and its IUF = 3 recovery.
2712    fn check_scaled_continuation<T: BesselFloat>(radii: &[f64], tol: f64) {
2713        use core::f64::consts::PI;
2714        let to64 = |z: Complex<T>| Complex::new(z.re.to_f64().unwrap(), z.im.to_f64().unwrap());
2715        // √(2/(πz)) on the principal branch (the reciprocal of z would flip
2716        // the sign of a zero imaginary part)
2717        let rt = |z: Complex<f64>| Complex::new((2.0 / PI).sqrt(), 0.0) / z.sqrt();
2718        // K_{n+1/2}(z)·e^z = √(π/(2z)) Σ_{k=0}^{n} (n+k)!/(k!(n−k)!) (2z)^−k
2719        let k_half = |n: usize, z: Complex<f64>| {
2720            let (mut sum, mut term, mut c) = (Complex::new(0.0, 0.0), Complex::new(1.0, 0.0), 1.0);
2721            for k in 0..=n {
2722                sum += term * c;
2723                c *= ((n + k + 1) * (n - k)) as f64 / (k + 1) as f64;
2724                term /= z * 2.0;
2725            }
2726            rt(z) * (PI / 2.0) * sum
2727        };
2728        let check =
2729            |what: &str, z: Complex<T>, v: Complex<T>, expected: Complex<f64>, scale: f64| {
2730                let err = (to64(v) - expected).norm() / scale;
2731                assert!(
2732                    err < tol,
2733                    "{what}: z={z:?}, value={v:?}, expected={expected:?}, err={err:e}"
2734                );
2735            };
2736        let i = Complex::new(0.0, 1.0);
2737        let half = T::from_f64(0.5);
2738        let three_halves = T::from_f64(1.5);
2739        for &r in radii {
2740            for d in [1e-3, 1.0, 5.0] {
2741                // K and I(−1/2) just left of the imaginary axis
2742                for z in [Complex::new(-d, r), Complex::new(-d, -r)] {
2743                    let z = Complex::new(T::from_f64(z.re), T::from_f64(z.im));
2744                    let z64 = to64(z);
2745                    let k0 = k_half(0, z64);
2746                    check("K 1/2", z, besselk_scaled(half, z).unwrap(), k0, k0.norm());
2747                    let k1 = k_half(1, z64);
2748                    check(
2749                        "K 3/2",
2750                        z,
2751                        besselk_scaled(three_halves, z).unwrap(),
2752                        k1,
2753                        k1.norm(),
2754                    );
2755                    let mut seq = [Complex::new(T::zero(), T::zero()); 5];
2756                    besselk_seq_into(half, z, Scaling::Exponential, &mut seq).unwrap();
2757                    for (n, &v) in seq.iter().enumerate() {
2758                        let k = k_half(n, z64);
2759                        check("K seq", z, v, k, k.norm());
2760                    }
2761                    // I_{−1/2}(z) = √(2/(πz))·cosh z, scaled by exp(−|Re z|);
2762                    // relative to √(2/(π|z|)), as cosh z can be small
2763                    let x = z64.re.abs();
2764                    let cosh = (Complex::from_polar((z64.re - x).exp(), z64.im)
2765                        + Complex::from_polar((-z64.re - x).exp(), -z64.im))
2766                        / 2.0;
2767                    let ri = rt(z64);
2768                    check(
2769                        "I -1/2",
2770                        z,
2771                        besseli_scaled(-half, z).unwrap(),
2772                        ri * cosh,
2773                        ri.norm(),
2774                    );
2775                }
2776                // H(1) below and H(2) above the real axis, and H(2) on its negative part
2777                for (z, h2) in [
2778                    (Complex::new(r, -d), false),
2779                    (Complex::new(-r, -d), false),
2780                    (Complex::new(r, d), true),
2781                    (Complex::new(-r, d), true),
2782                    (Complex::new(-r, 0.0), true),
2783                ] {
2784                    let z = Complex::new(T::from_f64(z.re), T::from_f64(z.im));
2785                    let z64 = to64(z);
2786                    let s = rt(z64);
2787                    let (f, e0, e1): (scaling_tests::F<T>, _, _) = if h2 {
2788                        (hankel2_scaled, i * s, -s * (1.0 - i / z64))
2789                    } else {
2790                        (hankel1_scaled, -i * s, -s * (1.0 + i / z64))
2791                    };
2792                    let name = if h2 { "H2" } else { "H1" };
2793                    check(name, z, f(half, z).unwrap(), e0, e0.norm());
2794                    check(name, z, f(three_halves, z).unwrap(), e1, e1.norm());
2795                }
2796            }
2797        }
2798    }
2799
2800    #[test]
2801    fn scaled_continuation_phase_f64() {
2802        // The old error is up to eps·|z|, ~2e-8 at |z| = 1e8
2803        check_scaled_continuation::<f64>(&[1e2, 1e4, 1e6, 1e8, 1e9], 1e-13);
2804    }
2805
2806    #[test]
2807    fn scaled_continuation_phase_f32() {
2808        // The old error reaches ~0.2 at |z| = 4e6
2809        check_scaled_continuation::<f32>(&[1e2, 1e3, 1e4, 1e6, 4e6], 2e-6);
2810    }
2811
2812    /// Y_{−ν} = sin(νπ)·J_ν + cos(νπ)·Y_ν next to a half-integer ν: the small
2813    /// cos(νπ) multiplies the large Y_ν, so it needs full relative accuracy.
2814    /// cospi used to lose it there (relative error ~ε/|cos(νπ)|).
2815    #[test]
2816    fn bessely_negative_order_near_half_integer() {
2817        // References from mpmath (40 digits); the old errors were 4.2e-8 and 4.8e-5
2818        let cases = [
2819            (
2820                -1.500000001,
2821                Complex::new(1e-5, 0.0),
2822                Complex::new(-0.07926656189549441, 0.0),
2823            ),
2824            (
2825                -20.499999999999,
2826                Complex::new(-3.0, 0.5),
2827                Complex::new(22.825467418360233, -109.6379263279235),
2828            ),
2829        ];
2830        for (nu, z, expected) in cases {
2831            let got = bessely(nu, z).unwrap();
2832            let err = (got - expected).norm() / expected.norm();
2833            assert!(err < 5e-14, "Y_{nu}({z}) = {got}, rel_err = {err:e}");
2834        }
2835    }
2836
2837    /// f32 version of `uniform_expansion_beyond_airy_range_f64`. Amos's
2838    /// single-precision algorithm keeps few correct digits at this |z| (as
2839    /// just below this range), so only the magnitudes are checked:
2840    /// |H(1,2)| ≈ √(2/(π|z|)) and |K| ≈ √(π/(2|z|)) for |z| ≫ ν², which rules
2841    /// out the former zeros. Half-integer orders avoid the 2·cos(νπ) term that
2842    /// H(2) picks up across the negative real axis; points on the axes
2843    /// are exact, as `from_polar` would put them off by ~0.3 in f32. Observed
2844    /// ratios are within 1%.
2845    #[test]
2846    fn uniform_expansion_beyond_airy_range_f32() {
2847        let polar = Complex::from_polar;
2848        for r in [3e6_f32, 4e6] {
2849            // The axes, and 0.3 rad on either side of them
2850            let points = [
2851                Complex::new(r, 0.0),
2852                Complex::new(-r, 0.0),
2853                Complex::new(0.0, r),
2854                Complex::new(0.0, -r),
2855                polar(r, 0.3),
2856                polar(r, 1.27),
2857                polar(r, 1.87),
2858                polar(r, 2.84),
2859                polar(r, -0.3),
2860                polar(r, -1.27),
2861                polar(r, -1.87),
2862                polar(r, -2.84),
2863            ];
2864            for (nu, z) in [35.5_f32, 40.5, 100.5]
2865                .into_iter()
2866                .flat_map(|nu| points.map(|z| (nu, z)))
2867            {
2868                let s = (z.norm() * core::f32::consts::FRAC_PI_2).sqrt();
2869                for (name, v) in [
2870                    ("H1", hankel1_scaled(nu, z).unwrap() * s),
2871                    ("H2", hankel2_scaled(nu, z).unwrap() * s),
2872                    (
2873                        "K",
2874                        besselk_scaled(nu, z).unwrap() * (s / core::f32::consts::FRAC_PI_2),
2875                    ),
2876                ] {
2877                    // On the negative real axis H(2) is continued from two
2878                    // terms of equal weight, whose f32 phase noise leaves
2879                    // even the magnitude off by up to ~2.7
2880                    if name == "H2" && z.im == 0.0 && z.re < 0.0 {
2881                        continue;
2882                    }
2883                    let m = v.norm();
2884                    assert!(
2885                        (0.5..2.0).contains(&m),
2886                        "{name}: nu={nu}, z={z:?}, ratio={m}"
2887                    );
2888                }
2889            }
2890        }
2891    }
2892}
2893
2894// ── Tests for _seq functions (negative orders, recurrence paths) ──
2895
2896#[cfg(test)]
2897mod axis_tests {
2898    //! Components of values on the real and imaginary axes, against
2899    //! independent oracles for the real functions of order m = |ν| at x > 0:
2900    //! power series (J, I), half-integer and integral formulas (Y, K).
2901    //! Expected values use the |ν|-basis formulas (DLMF, with the reflection
2902    //! formulas merged in); the signed-ν formulas would cancel for some rows.
2903    use super::*;
2904    use crate::algo::gamln::gamln;
2905    use crate::utils::{cospi, sinpi};
2906
2907    const PI: f64 = core::f64::consts::PI;
2908
2909    /// (sign, ln|Γ(t)|) for t not a non-positive integer.
2910    fn lgamma_sign(t: f64) -> (f64, f64) {
2911        if t > 0.0 {
2912            (1.0, gamln(t).unwrap())
2913        } else {
2914            let s = sinpi(t);
2915            (s.signum(), PI.ln() - s.abs().ln() - gamln(1.0 - t).unwrap())
2916        }
2917    }
2918
2919    /// exp(−shift)·Σ_k (∓1)^k (x/2)^{2k+ν} / (k!·Γ(k+ν+1)): J_ν (alternating)
2920    /// or I_ν, for any real ν that is not a negative integer.
2921    fn series(nu: f64, x: f64, alternating: bool, shift: f64) -> f64 {
2922        let lx = (x / 2.0).ln();
2923        let mut sum = 0.0;
2924        for k in 0..5000usize {
2925            let kf = k as f64;
2926            let (sg, lg) = lgamma_sign(kf + nu + 1.0);
2927            let lkf = gamln(kf + 1.0).unwrap();
2928            let mag = ((2.0 * kf + nu) * lx - lkf - lg - shift).exp();
2929            let term = if alternating && k % 2 == 1 {
2930                -sg * mag
2931            } else {
2932                sg * mag
2933            };
2934            sum += term;
2935            if kf > x && term.abs() <= 1e-18 * sum.abs() {
2936                return sum;
2937            }
2938        }
2939        panic!("series did not converge: nu={nu}, x={x}");
2940    }
2941
2942    fn near_integer(m: f64) -> bool {
2943        (m - m.round()).abs() < 1e-3
2944    }
2945
2946    fn half_integer(m: f64) -> bool {
2947        (m - 0.5).fract() == 0.0
2948    }
2949
2950    /// Y_m(x), if an accurate oracle exists (not for integer or near-integer m).
2951    fn y_oracle(m: f64, x: f64) -> Option<f64> {
2952        if half_integer(m) {
2953            // Y_{n+½} = (−1)^{n+1} J_{−n−½}
2954            let n = (m - 0.5) as i64;
2955            let sign = if n % 2 == 0 { -1.0 } else { 1.0 };
2956            Some(sign * series(-m, x, true, 0.0))
2957        } else if near_integer(m) || x >= m {
2958            None
2959        } else {
2960            // J_{−m} dominates J_m·cos for x < m: no cancellation
2961            Some((series(m, x, true, 0.0) * cospi(m) - series(-m, x, true, 0.0)) / sinpi(m))
2962        }
2963    }
2964
2965    /// exp(x)·K_m(x), if an accurate oracle exists.
2966    fn k_scaled_oracle(m: f64, x: f64) -> Option<f64> {
2967        if half_integer(m) {
2968            // K_{n+½}(x)·e^x = √(π/2x)·Σ_k (n+k)!/(k!(n−k)!(2x)^k)
2969            let n = (m - 0.5) as i64;
2970            let mut term = 1.0;
2971            let mut sum = 1.0;
2972            for k in 1..=n {
2973                let kf = k as f64;
2974                term *= ((n + k) as f64) * ((n - k + 1) as f64) / (kf * 2.0 * x);
2975                sum += term;
2976            }
2977            Some((PI / (2.0 * x)).sqrt() * sum)
2978        } else if x >= 1.0 && m <= 31.0 {
2979            // e^x·K_ν(x) = ∫₀^∞ exp(−2x·sinh²(t/2))·cosh(νt) dt (trapezoid)
2980            let h = (0.6 / x.sqrt()).min(0.1);
2981            let f = |t: f64| (-2.0 * x * (t / 2.0).sinh().powi(2)).exp() * (m * t).cosh();
2982            let mut sum = f(0.0) / 2.0;
2983            for k in 1..100_000 {
2984                let v = f(k as f64 * h);
2985                sum += v;
2986                if v <= 1e-18 * sum {
2987                    break;
2988                }
2989            }
2990            Some(sum * h)
2991        } else if !near_integer(m) && x < m.max(1.0) / 4.0 {
2992            // K_m = π/2·(I_{−m} − I_m)/sin(mπ); I_{−m} dominates for small x
2993            let d = series(-m, x, false, -x) - series(m, x, false, -x);
2994            Some(PI / 2.0 * d / sinpi(m))
2995        } else {
2996            None
2997        }
2998    }
2999
3000    /// Coefficients of (A, B) in the (real, imaginary) parts of `row` at
3001    /// order ν (written out from the DLMF formulas, independently of
3002    /// `axis::coefficients`).
3003    #[allow(clippy::type_complexity)]
3004    fn coeffs(row: &str, nu: f64) -> ((f64, f64), (f64, f64)) {
3005        let m = nu.abs();
3006        let (c, s) = (cospi(m), sinpi(m));
3007        let (c2, s2) = (cospi(2.0 * m), sinpi(2.0 * m));
3008        let (ch, sh) = (cospi(m / 2.0), sinpi(m / 2.0));
3009        let (c3, s3) = (cospi(1.5 * m), sinpi(1.5 * m));
3010        let neg = nu < 0.0;
3011        let h = PI / 2.0;
3012        let t = 2.0 / PI;
3013        // ((A re, B re), (A im, B im))
3014        match (row, neg) {
3015            ("H1+x", false) => ((1.0, 0.0), (0.0, 1.0)),
3016            ("H1+x", true) => ((c, -s), (s, c)),
3017            ("H2+x", false) => ((1.0, 0.0), (0.0, -1.0)),
3018            ("H2+x", true) => ((c, -s), (-s, -c)),
3019            ("H1-x", false) => ((-c, s), (s, c)),
3020            ("H1-x", true) => ((-1.0, 0.0), (0.0, 1.0)),
3021            ("H2-x", false) => ((3.0 * c, -s), (s, -c)),
3022            ("H2-x", true) => ((c2 + 2.0, -s2), (-s2, -c2)),
3023            ("Y-x", false) => ((0.0, c), (2.0 * c, -s)),
3024            ("Y-x", true) => ((s2 / 2.0, c * c), (c * c + 1.0, -s2 / 2.0)),
3025            ("K+ix", _) => ((-h * sh, -h * ch), (-h * ch, h * sh)),
3026            ("K-ix", _) => ((-h * sh, -h * ch), (h * ch, -h * sh)),
3027            ("H2+ix", false) => ((2.0 * ch, t * sh), (2.0 * sh, t * ch)),
3028            ("H2+ix", true) => ((2.0 * ch, t * s3), (-2.0 * sh, t * c3)),
3029            ("H1-ix", false) => ((2.0 * ch, t * sh), (-2.0 * sh, -t * ch)),
3030            ("H1-ix", true) => ((2.0 * ch, t * s3), (2.0 * sh, -t * c3)),
3031            _ => unreachable!(),
3032        }
3033    }
3034
3035    type F<T> = fn(T, Complex<T>) -> Result<Complex<T>, Error>;
3036
3037    /// (row, function, z from x, scaled with factor exp(−x)?, I·K basis?)
3038    type RowCase<T> = (&'static str, F<T>, fn(T) -> Complex<T>, bool, bool);
3039
3040    fn rows<T: BesselFloat>() -> [RowCase<T>; 13] {
3041        [
3042            (
3043                "H1+x",
3044                hankel1,
3045                |x| Complex::new(x, T::zero()),
3046                false,
3047                false,
3048            ),
3049            (
3050                "H2+x",
3051                hankel2,
3052                |x| Complex::new(x, T::zero()),
3053                false,
3054                false,
3055            ),
3056            (
3057                "H1-x",
3058                hankel1,
3059                |x| Complex::new(-x, T::zero()),
3060                false,
3061                false,
3062            ),
3063            (
3064                "H2-x",
3065                hankel2,
3066                |x| Complex::new(-x, T::zero()),
3067                false,
3068                false,
3069            ),
3070            (
3071                "Y-x",
3072                bessely,
3073                |x| Complex::new(-x, T::zero()),
3074                false,
3075                false,
3076            ),
3077            (
3078                "Y-x",
3079                bessely_scaled,
3080                |x| Complex::new(-x, T::zero()),
3081                false,
3082                false,
3083            ),
3084            (
3085                "K+ix",
3086                besselk,
3087                |x| Complex::new(T::zero(), x),
3088                false,
3089                false,
3090            ),
3091            (
3092                "K-ix",
3093                besselk,
3094                |x| Complex::new(T::zero(), -x),
3095                false,
3096                false,
3097            ),
3098            (
3099                "H2+ix",
3100                hankel2,
3101                |x| Complex::new(T::zero(), x),
3102                false,
3103                true,
3104            ),
3105            (
3106                "H2+ix",
3107                hankel2_scaled,
3108                |x| Complex::new(T::zero(), x),
3109                true,
3110                true,
3111            ),
3112            (
3113                "H1-ix",
3114                hankel1,
3115                |x| Complex::new(T::zero(), -x),
3116                false,
3117                true,
3118            ),
3119            (
3120                "H1-ix",
3121                hankel1_scaled,
3122                |x| Complex::new(T::zero(), -x),
3123                true,
3124                true,
3125            ),
3126            // Signed zeros as the real part of the imaginary axis
3127            (
3128                "K+ix",
3129                besselk,
3130                |x| Complex::new(-T::zero(), x),
3131                false,
3132                false,
3133            ),
3134        ]
3135    }
3136
3137    /// Check every component that has an oracle; returns the number checked.
3138    fn check_axis<T: BesselFloat>(
3139        nus: &[f64],
3140        jy_x: &[f64],
3141        ik_x: &[f64],
3142        tol: f64,
3143        tiny: f64,
3144    ) -> usize {
3145        let mut checked = 0;
3146        for (row, f, zf, scaled, ik) in rows::<T>() {
3147            for &nu_d in nus {
3148                let nu = T::from_f64(nu_d);
3149                let nu_d = nu.to_f64().unwrap();
3150                let m = nu_d.abs();
3151                let xs: &[f64] = if ik { ik_x } else { jy_x };
3152                for &xr in xs {
3153                    // Values below 1 are fractions of m for the J·Y rows
3154                    let x_d = if !ik && xr < 1.0 { xr * m } else { xr };
3155                    let x = T::from_f64(x_d);
3156                    let x_d = x.to_f64().unwrap();
3157                    // The J series cancels beyond x ≈ √(m+1)
3158                    if x_d == 0.0 || (!ik && x_d > (m / 2.0).min((m + 1.0).sqrt())) {
3159                        continue;
3160                    }
3161                    let Ok(v) = f(nu, zf(x)) else { continue };
3162                    let (a, b) = if ik {
3163                        let shift = if scaled { x_d } else { 0.0 };
3164                        let Some(k) = k_scaled_oracle(m, x_d) else {
3165                            continue;
3166                        };
3167                        (series(m, x_d, false, shift), k * (-x_d - shift).exp())
3168                    } else {
3169                        (
3170                            series(m, x_d, true, 0.0),
3171                            y_oracle(m, x_d).unwrap_or(f64::NAN),
3172                        )
3173                    };
3174                    let ((ar, br), (ai, bi)) = coeffs(row, nu_d);
3175                    for (got, ca, cb) in [(v.re, ar, br), (v.im, ai, bi)] {
3176                        let (ta, tb) = (ca * a, if cb == 0.0 { 0.0 } else { cb * b });
3177                        let expected = ta + tb;
3178                        // Skip where the two terms cancel (the oracle itself is then
3179                        // inaccurate), e.g. H2_{1.5}(i) = 0 as K_{3/2}(1) = π·I_{3/2}(1)
3180                        if !expected.is_finite()
3181                            || expected.abs() < tiny
3182                            || expected.abs() > 1e300
3183                            || expected.abs() < 1e-3 * (ta.abs() + tb.abs())
3184                        {
3185                            continue;
3186                        }
3187                        let got = got.to_f64().unwrap();
3188                        let err = ((got - expected) / expected).abs();
3189                        assert!(
3190                            err < tol,
3191                            "{row}: nu={nu_d}, x={x_d}: got {got:e}, expected {expected:e}, rel {err:e}"
3192                        );
3193                        checked += 1;
3194                    }
3195                }
3196            }
3197        }
3198        checked
3199    }
3200
3201    // Includes orders a little away from integers (2.02, 3.97, ...), where
3202    // the small sin(νπ) coefficients of Y take part
3203    const NUS: [f64; 41] = [
3204        0.5, -0.5, 1.5, -1.5, 2.5, -2.5, 5.5, -5.5, 10.5, -10.5, 30.5, -30.5, 90.5, 1.0, -1.0, 2.0,
3205        -2.0, 10.0, -10.0, 0.3, -0.3, 3.3, -3.3, 10.7, -10.7, 2.000001, -1.999999, 2.500001,
3206        -2.499999, 1.000001, -0.999999, 20.0, 85.5, 2.02, -2.02, 3.97, -3.97, 10.01, -10.01, 1.49,
3207        -2.51,
3208    ];
3209
3210    #[test]
3211    fn axis_components_f64() {
3212        // J·Y rows: x as fractions of m (≤ m/2) and absolute small values
3213        let jy_x = [1e-10, 1e-5, 1e-2, 0.1, 0.3, 0.5];
3214        let ik_x = [1e-5, 1e-2, 0.3, 1.0, 5.0, 20.0, 60.0, 200.0, 690.0];
3215        let n = check_axis::<f64>(&NUS, &jy_x, &ik_x, 1e-11, 1e-280);
3216        assert!(n > 2000, "only {n} components checked");
3217    }
3218
3219    #[test]
3220    fn axis_components_f32() {
3221        let nus = [0.5, -0.5, 2.5, -2.5, 5.5, 10.5, 1.0, -2.0, 3.3, -3.3];
3222        let jy_x = [1e-3, 0.1, 0.4];
3223        let ik_x = [1e-2, 1.0, 10.0, 40.0];
3224        let n = check_axis::<f32>(&nus, &jy_x, &ik_x, 1e-4, 1e-28);
3225        assert!(n > 200, "only {n} components checked");
3226    }
3227
3228    /// An (I, K) component at |small|/|large| = 5e-3 is recomputed: Amos's
3229    /// Im H^(2)_ν(ix) here is off by 5.2e-14, the recomputed one by < 1e-15
3230    /// (mpmath reference).
3231    #[test]
3232    fn axis_ik_ratio_between_1e3_and_1e2() {
3233        let h = hankel2(9.999_999_99f64, Complex::new(0.0, 8.199_999_991_8)).unwrap();
3234        let err = ((h.im + 0.015700603715088221) / 0.015700603715088221).abs();
3235        assert!(err < 1e-14, "Im H2_9.99999999(8.2i): error {err:e}");
3236    }
3237
3238    /// Re Y_ν(−x) for x ≥ |ν| next to half-integer orders is cos(νπ)·Y_ν(x),
3239    /// with a nearly vanishing cos(νπ). Compared against `bessely` on the
3240    /// positive real axis (a different Amos path, reflected with cospi for
3241    /// ν < 0), skipping x next to zeros of Y_ν. Amos's own Re Y_ν(−x) fails
3242    /// 248 of these f64 checks, with relative errors up to 0.8 (f32: 0.9).
3243    fn check_y_neg_axis<T: BesselFloat + core::fmt::Display>(
3244        nus: &[f64],
3245        deltas: &[f64],
3246        fracs: &[f64],
3247        tol: f64,
3248    ) -> usize {
3249        let mut checked = 0;
3250        for &nu0 in nus {
3251            for &d in deltas {
3252                for sign in [1.0, -1.0] {
3253                    let nu = T::from_f64(sign * (nu0 + d));
3254                    let m = nu.abs().to_f64().unwrap();
3255                    for &frac in fracs {
3256                        let x = T::from_f64(m * frac + 0.3);
3257                        let (Ok(v), Ok(y)) = (
3258                            bessely(nu, Complex::new(-x, T::zero())),
3259                            bessely(nu, Complex::new(x, T::zero())),
3260                        ) else {
3261                            continue;
3262                        };
3263                        let j = besselj(nu, Complex::new(x, T::zero())).unwrap().re;
3264                        let (y, j) = (y.re.to_f64().unwrap(), j.to_f64().unwrap());
3265                        if y.abs() < 0.1 * j.abs() {
3266                            continue;
3267                        }
3268                        let expected = cospi(nu).to_f64().unwrap() * y;
3269                        let err = ((v.re.to_f64().unwrap() - expected) / expected).abs();
3270                        assert!(
3271                            err < tol,
3272                            "nu={nu}, x={x}: Re Y(-x) = {}, expected {expected:e}",
3273                            v.re
3274                        );
3275                        checked += 1;
3276                    }
3277                }
3278            }
3279        }
3280        checked
3281    }
3282
3283    #[test]
3284    fn axis_y_neg_beyond_order_f64() {
3285        let nus = [0.5, 1.5, 4.5, 10.5, 30.5, 70.5];
3286        let deltas = [1e-13, 1e-10, -1e-7, 1e-5, -3e-4, 1e-3];
3287        let fracs = [1.0, 1.1, 1.5, 3.0, 8.0];
3288        let n = check_y_neg_axis::<f64>(&nus, &deltas, &fracs, 1e-11);
3289        assert!(n > 250, "only {n} values checked");
3290    }
3291
3292    #[test]
3293    fn axis_y_neg_beyond_order_f32() {
3294        let nus = [0.5, 1.5, 4.5, 10.5];
3295        let deltas = [3e-6, -3e-5, 3e-4];
3296        let fracs = [1.0, 1.5, 3.0];
3297        let n = check_y_neg_axis::<f32>(&nus, &deltas, &fracs, 1e-4);
3298        assert!(n > 40, "only {n} values checked");
3299    }
3300
3301    /// In a sequence the order of element j is exactly ν + j (from ν + nc
3302    /// for ν < 0, as the sequence computes it), in general not a float. Next
3303    /// to a half-integer, Re Y_μ(−x) = cos(μπ)·Y_μ(x) with
3304    /// cos(μπ) = (−1)^p·cos(base·π) needs that exact order: an ulp of μ is a
3305    /// relative error of ulp/|cos(μπ)| (e.g. 2.8e-9 at order 4.4999999 from
3306    /// ν = 0.4999999 at −6).
3307    /// Y_μ(x) comes from the sequence on the positive real axis (the same
3308    /// orders); x next to zeros of Y_μ is skipped. Returns the number of
3309    /// checked elements whose order is not a float.
3310    fn check_y_neg_seq_orders<T: BesselFloat + core::fmt::Display>(
3311        nus: &[f64],
3312        xs: &[f64],
3313        tol: f64,
3314    ) -> usize {
3315        const N: usize = 30;
3316        let czero = Complex::new(T::zero(), T::zero());
3317        let (mut neg, mut pos, mut jpos) = ([czero; N], [czero; N], [czero; N]);
3318        let mut inexact = 0;
3319        for &nu in nus {
3320            let nu = T::from_f64(nu);
3321            let nc = if nu < T::zero() {
3322                nu.abs().ceil().to_usize().unwrap()
3323            } else {
3324                0
3325            };
3326            let base = nu + T::from_f64(nc as f64);
3327            for &x in xs {
3328                let x = T::from_f64(x);
3329                let (zn, zp) = (Complex::new(-x, T::zero()), Complex::new(x, T::zero()));
3330                bessely_seq_into(nu, zn, Scaling::Unscaled, &mut neg).unwrap();
3331                bessely_seq_into(nu, zp, Scaling::Unscaled, &mut pos).unwrap();
3332                besselj_seq_into(nu, zp, Scaling::Unscaled, &mut jpos).unwrap();
3333                for j in nc..N {
3334                    let p = j - nc;
3335                    let (y, jv) = (pos[j].re, jpos[j].re);
3336                    if y.abs() < T::from_f64(0.1) * jv.abs() {
3337                        continue;
3338                    }
3339                    let sign = if p % 2 == 0 { T::one() } else { -T::one() };
3340                    let expected = sign * cospi(base) * y;
3341                    let err = ((neg[j].re - expected) / expected).abs().to_f64().unwrap();
3342                    assert!(
3343                        err < tol,
3344                        "nu={nu}, x={x}, j={j}: Re Y(-x) = {}, expected {expected}, rel {err:e}",
3345                        neg[j].re
3346                    );
3347                    // Whether base + p is a float
3348                    let pt = T::from_f64(p as f64);
3349                    let s = base + pt;
3350                    let bb = s - base;
3351                    if (base - (s - bb)) + (pt - bb) != T::zero() {
3352                        inexact += 1;
3353                    }
3354                }
3355            }
3356        }
3357        inexact
3358    }
3359
3360    #[test]
3361    fn axis_y_neg_seq_orders_off_the_float_grid() {
3362        let nus = [0.4999999, 0.50000031, 2.4999997, -2.4999999, -0.50000013];
3363        let n = check_y_neg_seq_orders::<f64>(&nus, &[0.3, 6.0, 15.0, 40.0], 1e-12);
3364        assert!(n > 200, "only {n} inexact orders checked");
3365        let nus = [0.4999, 0.50031, 2.4997, -2.4999, -0.50013];
3366        let n = check_y_neg_seq_orders::<f32>(&nus, &[2.0, 6.0, 15.0], 1e-4);
3367        assert!(n > 150, "only {n} inexact orders checked");
3368    }
3369
3370    /// As `check_y_neg_seq_orders` for K_μ(ix) = −(π/2)·((s·J + c·Y) +
3371    /// i(c·J − s·Y)) with (s, c) = (sin, cos)(μπ/2), x < μ: next to an
3372    /// integer μ one of s, c nearly vanishes. They come from those of base/2
3373    /// by p quarter turns; J and Y from sequences on the positive real axis.
3374    fn check_k_imag_seq_orders<T: BesselFloat + core::fmt::Display>(
3375        nus: &[f64],
3376        xs: &[f64],
3377        tol: f64,
3378    ) -> usize {
3379        const N: usize = 30;
3380        let czero = Complex::new(T::zero(), T::zero());
3381        let (mut k, mut ypos, mut jpos) = ([czero; N], [czero; N], [czero; N]);
3382        let hpi = T::from_f64(PI / 2.0);
3383        let mut checked = 0;
3384        for &nu in nus {
3385            let nu = T::from_f64(nu);
3386            let nc = if nu < T::zero() {
3387                nu.abs().ceil().to_usize().unwrap()
3388            } else {
3389                0
3390            };
3391            let base = nu + T::from_f64(nc as f64);
3392            let half = base / T::from_f64(2.0);
3393            let (s0, c0) = (sinpi(half), cospi(half));
3394            for &x in xs {
3395                let x = T::from_f64(x);
3396                let zp = Complex::new(x, T::zero());
3397                besselk_seq_into(nu, Complex::new(T::zero(), x), Scaling::Unscaled, &mut k)
3398                    .unwrap();
3399                bessely_seq_into(nu, zp, Scaling::Unscaled, &mut ypos).unwrap();
3400                besselj_seq_into(nu, zp, Scaling::Unscaled, &mut jpos).unwrap();
3401                for j in nc..N {
3402                    let p = j - nc;
3403                    let m = base + T::from_f64(p as f64);
3404                    if x >= m {
3405                        continue;
3406                    }
3407                    let (s, c) = match p % 4 {
3408                        0 => (s0, c0),
3409                        1 => (c0, -s0),
3410                        2 => (-s0, -c0),
3411                        _ => (-c0, s0),
3412                    };
3413                    let (jv, y) = (jpos[j].re, ypos[j].re);
3414                    let terms = [(s * jv, c * y), (c * jv, -(s * y))];
3415                    let got = [k[j].re, k[j].im];
3416                    // The small component, where its two terms do not cancel
3417                    let (i, (ta, tb)) = if got[0].abs() < got[1].abs() {
3418                        (0, terms[0])
3419                    } else {
3420                        (1, terms[1])
3421                    };
3422                    let expected = -hpi * (ta + tb);
3423                    if got[i].abs() >= T::from_f64(0.1) * got[1 - i].abs()
3424                        || (ta + tb).abs() < T::from_f64(0.1) * (ta.abs() + tb.abs())
3425                    {
3426                        continue;
3427                    }
3428                    let err = ((got[i] - expected) / expected).abs().to_f64().unwrap();
3429                    assert!(
3430                        err < tol,
3431                        "nu={nu}, x={x}, j={j}: K(ix) = {}, expected {expected} in part {i}, rel {err:e}",
3432                        k[j]
3433                    );
3434                    checked += 1;
3435                }
3436            }
3437        }
3438        checked
3439    }
3440
3441    #[test]
3442    fn axis_k_imag_seq_orders_off_the_float_grid() {
3443        let nus = [1.9999997, 3.00000031, 0.9999997, -2.0000001, -1.00000013];
3444        let n = check_k_imag_seq_orders::<f64>(&nus, &[0.3, 2.0, 6.0], 1e-12);
3445        assert!(n > 150, "only {n} components checked");
3446        let nus = [1.9997, 3.00031, 0.9997, -2.0001, -1.00013];
3447        let n = check_k_imag_seq_orders::<f32>(&nus, &[2.0, 6.0], 1e-4);
3448        assert!(n > 100, "only {n} components checked");
3449    }
3450
3451    /// Whether the J·Y rule recomputes component k (0 = real, 1 = imaginary)
3452    /// of `kind` ('Y', or '2' for unscaled H^(2)) at −x for x ≥ |ν|: Y_m
3453    /// alone (J's coefficient below 1e-3 of Y's) for m < 18, or Re Y(−x)
3454    /// with coefficients summing below 0.1. The coefficients are those of
3455    /// J_m and Y_m (DLMF 10.4.6, 10.11.2/5).
3456    fn neg_axis_selected(kind: char, nu: f64, k: usize) -> bool {
3457        let m = nu.abs();
3458        let (c, s) = (cospi(m), sinpi(m));
3459        let (c2, s2) = (cospi(2.0 * m), sinpi(2.0 * m));
3460        let (a, b) = match (kind, nu < 0.0, k) {
3461            ('Y', false, 0) => (0.0, c),
3462            ('Y', false, _) => (2.0 * c, -s),
3463            ('Y', true, 0) => (s2 / 2.0, c * c),
3464            ('Y', true, _) => (c * c + 1.0, -s2 / 2.0),
3465            ('2', false, 0) => (3.0 * c, -s),
3466            ('2', false, _) => (s, -c),
3467            ('2', true, 0) => (c2 + 2.0, -s2),
3468            ('2', true, _) => (-s2, -c2),
3469            _ => return false,
3470        };
3471        (m < 18.0 && a.abs() < 1e-3 * b.abs()) || (kind == 'Y' && k == 0 && a.abs() + b.abs() < 0.1)
3472    }
3473
3474    /// Components of Y(−x) and H^(2)(−x) beyond the order that are Y_m alone
3475    /// (at or next to integer and half-integer orders, and Re Y(−x) for
3476    /// ν ≥ 0), next to their zeros: single values and the same element of a
3477    /// sequence of 45, crossing ν = 0 where the orders allow it exactly. Errors in units of ε·(|α| + |β|)·|H_m(x)|,
3478    /// the error to expect of a normwise accurate value: Amos's are 31 to
3479    /// 1746 here (f32 up to 13809), except at the two points of
3480    /// ν = −1 − 5e-8 and −9 (11.6 and 3.5), the recomputed ones, single and
3481    /// in sequences, at most 7.1 (mpmath references).
3482    #[test]
3483    fn axis_neg_next_to_zeros() {
3484        type Case = (char, f64, f64, usize, f64, f64);
3485        fn check<T: BesselFloat + core::fmt::Display>(cases: &[Case]) {
3486            let czero = Complex::new(T::zero(), T::zero());
3487            let mut buf = [czero; 45];
3488            for &(kind, nu, x, k, want, scale) in cases {
3489                let z = Complex::new(T::from_f64(-x), T::zero());
3490                let tnu = T::from_f64(nu);
3491                // Element j of a sequence from ν − j crossing ν = 0, with the
3492                // largest j ≤ 20 whose order is exactly ν (ν < 0 crosses with
3493                // j = 0; the order of a negative element is |ν0| − j)
3494                let exact = |j: usize| {
3495                    let (jt, nu0) = (T::from_f64(j as f64), tnu - T::from_f64(j as f64));
3496                    if nu < 0.0 {
3497                        nu0.abs() - jt == tnu.abs()
3498                    } else {
3499                        nu0 + jt == tnu
3500                    }
3501                };
3502                let j = (0..=20).rev().find(|&j| exact(j)).unwrap();
3503                let nu0 = tnu - T::from_f64(j as f64);
3504                let (singles, seq) = if kind == 'Y' {
3505                    let seq = bessely_seq_into(nu0, z, Scaling::Unscaled, &mut buf);
3506                    let s = [bessely(tnu, z).unwrap(), bessely_scaled(tnu, z).unwrap()];
3507                    (s, seq)
3508                } else {
3509                    let seq = hankel2_seq_into(nu0, z, Scaling::Unscaled, &mut buf);
3510                    let v = hankel2(tnu, z).unwrap();
3511                    ([v, v], seq)
3512                };
3513                seq.unwrap();
3514                for v in singles.into_iter().chain([buf[j]]) {
3515                    let got = if k == 0 { v.re } else { v.im };
3516                    let units = (got.to_f64().unwrap() - want).abs()
3517                        / (T::epsilon().to_f64().unwrap() * scale);
3518                    assert!(
3519                        units < 16.0,
3520                        "{kind} nu={nu}, x={x}, k={k}: {v}, {units} units"
3521                    );
3522                }
3523            }
3524        }
3525        // (function, ν, x, component, value, (|α| + |β|)·|H_m(x)|)
3526        check::<f64>(&[
3527            (
3528                'Y',
3529                0.5404395744314369,
3530                14.199204647196062,
3531                0,
3532                1.7790766124893162e-07,
3533                0.02683,
3534            ),
3535            (
3536                'Y',
3537                5.4579621742598405,
3538                17.955980580074737,
3539                0,
3540                6.434329040379513e-07,
3541                0.02539,
3542            ),
3543            (
3544                'Y',
3545                8.553187617888364,
3546                14.839639619602085,
3547                0,
3548                6.376630874085129e-06,
3549                0.03803,
3550            ),
3551            (
3552                'Y',
3553                10.466204201871951,
3554                20.809512383347556,
3555                0,
3556                -0.0003560905884186947,
3557                0.01992,
3558            ),
3559            (
3560                'Y',
3561                12.541375369126225,
3562                19.383109940166836,
3563                0,
3564                -0.0010992113067365456,
3565                0.02685,
3566            ),
3567            (
3568                'Y',
3569                15.457226611852212,
3570                17.934577879174395,
3571                0,
3572                -0.0009456667810636375,
3573                0.03461,
3574            ),
3575            (
3576                'Y',
3577                -1.0000000505613758,
3578                18.058798586324336,
3579                0,
3580                -0.002889004423008248,
3581                0.1879,
3582            ),
3583            (
3584                'Y',
3585                -9.0,
3586                15.361301343580108,
3587                0,
3588                -7.680400842858634e-13,
3589                0.2257,
3590            ),
3591            (
3592                'Y',
3593                -1e-08,
3594                13.361097489864841,
3595                0,
3596                6.9195873309076905e-09,
3597                0.2182,
3598            ),
3599            (
3600                'Y',
3601                -14.0,
3602                84.44679697015918,
3603                0,
3604                -1.9655202108533837e-08,
3605                0.08743,
3606            ),
3607            (
3608                'Y',
3609                -16.0002,
3610                100.03440128478886,
3611                0,
3612                -5.04102359239503e-05,
3613                0.08034,
3614            ),
3615            (
3616                'Y',
3617                16.5001,
3618                100.74840631508634,
3619                1,
3620                -1.572667517633897e-11,
3621                0.08008,
3622            ),
3623            (
3624                'Y',
3625                16.50001,
3626                100.74885123627298,
3627                1,
3628                3.4050198315155154e-12,
3629                0.08004,
3630            ),
3631            (
3632                '2',
3633                2.0,
3634                13.209986713986947,
3635                1,
3636                8.25474504044067e-10,
3637                0.2207,
3638            ),
3639            (
3640                '2',
3641                14.0,
3642                84.44679697015918,
3643                1,
3644                1.9655202108533837e-08,
3645                0.08743,
3646            ),
3647            (
3648                '2',
3649                17.0,
3650                56.32193676896589,
3651                1,
3652                1.067480680320756e-12,
3653                0.1089,
3654            ),
3655            (
3656                '2',
3657                5.5,
3658                7.293692200815263,
3659                0,
3660                2.0120730332658653e-11,
3661                0.3557,
3662            ),
3663            (
3664                '2',
3665                14.5,
3666                85.15744139214142,
3667                0,
3668                3.7729005022425324e-05,
3669                0.0871,
3670            ),
3671            (
3672                '2',
3673                -5.5,
3674                21.283248938558053,
3675                1,
3676                1.5177917276336462e-11,
3677                0.1759,
3678            ),
3679            (
3680                '2',
3681                -17.5,
3682                60.27562453458215,
3683                1,
3684                -1.0842178117582853e-11,
3685                0.1051,
3686            ),
3687            (
3688                '2',
3689                0.0001,
3690                13.360940502860311,
3691                1,
3692                0.0001371041719352024,
3693                0.2183,
3694            ),
3695            (
3696                '2',
3697                -16.5001,
3698                100.74840631508634,
3699                1,
3700                2.0689729735150853e-11,
3701                0.08008,
3702            ),
3703            (
3704                '2',
3705                16.5001,
3706                100.74840631508634,
3707                0,
3708                -2.5143155227226787e-05,
3709                0.08011,
3710            ),
3711        ]);
3712        check::<f32>(&[
3713            (
3714                'Y',
3715                0.42830756306648254,
3716                7.750723838806152,
3717                0,
3718                0.0003261815181377399,
3719                0.06399,
3720            ),
3721            (
3722                'Y',
3723                3.5858168601989746,
3724                8.841499328613281,
3725                0,
3726                3.636876710628325e-05,
3727                0.0746,
3728            ),
3729            (
3730                'Y',
3731                11.286245346069336,
3732                38.86476516723633,
3733                0,
3734                0.0005308631669817854,
3735                0.0814,
3736            ),
3737            (
3738                'Y',
3739                12.348158836364746,
3740                40.268272399902344,
3741                0,
3742                8.929377383150825e-08,
3743                0.05917,
3744            ),
3745            (
3746                'Y',
3747                17.424158096313477,
3748                53.573184967041016,
3749                0,
3750                6.935261359731625e-07,
3751                0.02646,
3752            ),
3753            (
3754                'Y',
3755                -13.999799728393555,
3756                39.09739303588867,
3757                0,
3758                0.00014275489424510885,
3759                0.1321,
3760            ),
3761            (
3762                'Y',
3763                -17.0,
3764                49.711029052734375,
3765                0,
3766                0.0025988763574300126,
3767                0.1167,
3768            ),
3769            (
3770                'Y',
3771                14.500100135803223,
3772                39.73927688598633,
3773                1,
3774                -1.494391009619099e-06,
3775                0.1312,
3776            ),
3777            (
3778                '2',
3779                17.0,
3780                49.711029052734375,
3781                1,
3782                0.0025988763574300126,
3783                0.1167,
3784            ),
3785            (
3786                '2',
3787                12.0,
3788                39.807090759277344,
3789                1,
3790                -5.3511832603744025e-06,
3791                0.1295,
3792            ),
3793            (
3794                '2',
3795                16.5,
3796                45.684940338134766,
3797                0,
3798                -3.289102085362203e-06,
3799                0.1222,
3800            ),
3801            (
3802                '2',
3803                -16.500099182128906,
3804                38.856502532958984,
3805                1,
3806                4.2589171368920826e-05,
3807                0.1346,
3808            ),
3809        ]);
3810    }
3811
3812    /// Values on the negative real axis beyond the order with a small
3813    /// component the rule leaves alone are Amos's, bit for bit (mpmath
3814    /// errors in units as in `axis_neg_next_to_zeros`, Amos → recomputed):
3815    /// - Re Y_41(−x) next to a zero of Y_41 (relative errors 5.5e-15 →
3816    ///   2.2e-13).
3817    /// - Components whose J coefficient is not small: Im Y(−x) and
3818    ///   Re Y(−x) for ν < 0 next to their zeros (e.g. Re Y_{−7.9}, 0.1 →
3819    ///   73 units), with J/Y ratios 3e-3 and 1.3e-3 just above the limit,
3820    ///   and J alone (Im H^(2)_{16.5}(−x) = J_{16.5}(x)).
3821    /// - Y alone above order 18 (Im H^(2)_20(−x)), and in H^(1)(−x), where
3822    ///   Amos's value is as accurate.
3823    #[test]
3824    fn axis_neg_keeps_amos() {
3825        for (kind, nu, x) in [
3826            ('Y', 41.0f64, 50.623088092303156),
3827            ('Y', 0.7876708575032857, 29.094981102136426),
3828            ('Y', 2.000000000007951, 24.27010247905739),
3829            ('Y', -7.9, 18.043947370535463),
3830            ('Y', -16.999, 59.607268325147366),
3831            ('2', -16.0002, 100.03440128478886),
3832            ('2', 16.5, 99.21561951113041),
3833            ('2', 20.0, 56.92777653004243),
3834            ('1', -17.4999, 29.133873186247307),
3835        ] {
3836            let z = Complex::new(-x, 0.0);
3837            let pairs = if kind == 'Y' {
3838                [
3839                    (bessely(nu, z), bessely_value(nu, z, Scaling::Unscaled)),
3840                    (
3841                        bessely_scaled(nu, z),
3842                        bessely_value(nu, z, Scaling::Exponential),
3843                    ),
3844                ]
3845            } else {
3846                let (f, h): (F<f64>, _) = if kind == '1' {
3847                    (hankel1, HankelKind::First)
3848                } else {
3849                    (hankel2, HankelKind::Second)
3850                };
3851                let p = (f(nu, z), hankel_value(h, nu, z, Scaling::Unscaled));
3852                [p, p]
3853            };
3854            for (v, r) in pairs {
3855                let (v, r) = (v.unwrap(), r.unwrap());
3856                assert_eq!(
3857                    (v.re.to_bits(), v.im.to_bits()),
3858                    (r.re.to_bits(), r.im.to_bits()),
3859                    "{kind} nu={nu}, x={x}"
3860                );
3861            }
3862        }
3863    }
3864
3865    /// Re H(ν, x) against `besselj`: a different Amos path (J from zbinu,
3866    /// H from K(−iz)).
3867    #[test]
3868    fn hankel_real_part_matches_besselj() {
3869        for nu in [1.0, 5.0, 10.0, 30.5, -10.0, -3.3, 90.0, 150.5] {
3870            for frac in [1e-6, 0.01, 0.3, 0.9] {
3871                let z = Complex::new(frac * f64::abs(nu), 0.0);
3872                let j = besselj(nu, z).unwrap().re;
3873                if j == 0.0 {
3874                    continue;
3875                }
3876                for h in [hankel1(nu, z).unwrap(), hankel2(nu, z).unwrap()] {
3877                    let err = ((h.re - j) / j).abs();
3878                    assert!(
3879                        err < 1e-11,
3880                        "nu={nu}, x={}: Re H = {:e}, J = {j:e}",
3881                        z.re,
3882                        h.re
3883                    );
3884                }
3885            }
3886        }
3887    }
3888
3889    /// J and Y of real order are exactly real on the positive real axis.
3890    #[test]
3891    fn bessel_jy_real_on_positive_axis() {
3892        let mut out = [Complex::new(0.0f64, 0.0); 9];
3893        for nu in [0.0f64, 0.3, 1.0, 5.5, 120.0, -0.3, -2.0, -7.5, -40.25] {
3894            for x in [1e-8f64, 0.5, 5.0, 37.0, 1e3, 1e5] {
3895                let z = Complex::new(x, 0.0);
3896                for f in [besselj, besselj_scaled, bessely, bessely_scaled] {
3897                    if let Ok(v) = f(nu, z) {
3898                        assert_eq!(v.im.to_bits(), 0, "nu={nu}, x={x}: {v}");
3899                    }
3900                }
3901                for s in [Scaling::Unscaled, Scaling::Exponential] {
3902                    if besselj_seq_into(nu, z, s, &mut out).is_ok() {
3903                        assert!(
3904                            out.iter().all(|v| v.im.to_bits() == 0),
3905                            "J seq nu={nu}, x={x}"
3906                        );
3907                    }
3908                    if bessely_seq_into(nu, z, s, &mut out).is_ok() {
3909                        assert!(
3910                            out.iter().all(|v| v.im.to_bits() == 0),
3911                            "Y seq nu={nu}, x={x}"
3912                        );
3913                    }
3914                }
3915            }
3916        }
3917    }
3918
3919    /// Values the rule does not select are Amos's own, bit for bit: off the
3920    /// axes, for the J·Y rows at x ≥ m (except components of Y(−x) and
3921    /// H^(2)(−x) that `neg_axis_selected`), and where the components are not
3922    /// far apart in size (ratio ≥ 0.1 for J·Y, ≥ 1e-2 for I·K).
3923    #[test]
3924    fn axis_fix_leaves_other_values_alone() {
3925        type Raw = fn(f64, Complex<f64>, Scaling) -> Result<Complex<f64>, Error>;
3926        let raw_h1: Raw = |nu, z, s| hankel_value(HankelKind::First, nu, z, s);
3927        let raw_h2: Raw = |nu, z, s| hankel_value(HankelKind::Second, nu, z, s);
3928        // (function, Amos's value, scaling, 'H' / '2' (unscaled H^(2)) / 'K' / 'Y')
3929        let fns: [(F<f64>, Raw, Scaling, char); 7] = [
3930            (hankel1, raw_h1, Scaling::Unscaled, 'H'),
3931            (hankel2, raw_h2, Scaling::Unscaled, '2'),
3932            (hankel1_scaled, raw_h1, Scaling::Exponential, 'H'),
3933            (hankel2_scaled, raw_h2, Scaling::Exponential, 'H'),
3934            (besselk, besselk_value, Scaling::Unscaled, 'K'),
3935            (bessely, bessely_value, Scaling::Unscaled, 'Y'),
3936            (bessely_scaled, bessely_value, Scaling::Exponential, 'Y'),
3937        ];
3938        let tiny = f64::MIN_POSITIVE;
3939        for (f, raw, scaling, kind) in fns {
3940            for nu in [0.0, 0.3, 2.0, 5.5, 20.0, -0.3, -2.0, -5.5, -20.0, 90.5] {
3941                for x in [0.01, 0.7, 1.9, 6.0, 25.0, 100.0] {
3942                    let zs = [
3943                        Complex::new(x, 0.0),
3944                        Complex::new(-x, 0.0),
3945                        Complex::new(0.0, x),
3946                        Complex::new(0.0, -x),
3947                        // Just off the axes
3948                        Complex::new(x, tiny),
3949                        Complex::new(-x, -tiny),
3950                        Complex::new(tiny, x),
3951                        Complex::new(-tiny, -x),
3952                        Complex::new(x, x),
3953                    ];
3954                    for (iz, z) in zs.into_iter().enumerate() {
3955                        let (Ok(v), Ok(r)) = (f(nu, z), raw(nu, z, scaling)) else {
3956                            continue;
3957                        };
3958                        let (sr, si) = (r.re.abs(), r.im.abs());
3959                        let ratio = sr.min(si) / sr.max(si);
3960                        let imag_axis = z.re == 0.0;
3961                        // H on the imaginary axis is an I·K row; the rest are J·Y
3962                        let ik = imag_axis && matches!(kind, 'H' | '2');
3963                        let selectable = iz < 4
3964                            && if ik {
3965                                ratio < 1e-2
3966                            } else if x < f64::abs(nu) {
3967                                ratio < 0.1
3968                            } else {
3969                                iz == 1
3970                                    && ratio < 0.1
3971                                    && neg_axis_selected(kind, nu, usize::from(sr >= si))
3972                            };
3973                        // Y is made exactly real on the positive real axis
3974                        let r = if kind == 'Y' && iz == 0 {
3975                            Complex::new(r.re, 0.0)
3976                        } else {
3977                            r
3978                        };
3979                        if !selectable {
3980                            assert!(
3981                                v.re.to_bits() == r.re.to_bits()
3982                                    && v.im.to_bits() == r.im.to_bits(),
3983                                "nu={nu}, z={z}: {v} vs Amos {r}"
3984                            );
3985                        }
3986                    }
3987                }
3988            }
3989        }
3990    }
3991
3992    type SeqF = fn(f64, Complex<f64>, Scaling, &mut [Complex<f64>]) -> Result<SeqInfo, Error>;
3993
3994    /// `_seq_into` against single values on the axes, across chunk boundaries
3995    /// and orders crossing zero. Where the small component is clearly
3996    /// recomputed by both, compare it componentwise; otherwise normwise
3997    /// (Amos's own seq and single values differ by up to ~2e-13 normwise).
3998    #[test]
3999    fn axis_seq_matches_single() {
4000        // (.., ratio, function for `neg_axis_selected` beyond x = m, or ' ')
4001        type SeqCase = (SeqF, F<f64>, Scaling, fn(f64) -> Complex<f64>, f64, char);
4002        let cases: [SeqCase; 9] = [
4003            (
4004                hankel1_seq_into,
4005                hankel1,
4006                Scaling::Unscaled,
4007                |x| Complex::new(x, 0.0),
4008                0.1,
4009                ' ',
4010            ),
4011            (
4012                hankel2_seq_into,
4013                hankel2,
4014                Scaling::Unscaled,
4015                |x| Complex::new(-x, 0.0),
4016                0.1,
4017                '2',
4018            ),
4019            (
4020                bessely_seq_into,
4021                bessely,
4022                Scaling::Unscaled,
4023                |x| Complex::new(-x, 0.0),
4024                0.1,
4025                'Y',
4026            ),
4027            (
4028                bessely_seq_into,
4029                bessely_scaled,
4030                Scaling::Exponential,
4031                |x| Complex::new(-x, 0.0),
4032                0.1,
4033                'Y',
4034            ),
4035            (
4036                besselk_seq_into,
4037                besselk,
4038                Scaling::Unscaled,
4039                |x| Complex::new(0.0, x),
4040                0.1,
4041                ' ',
4042            ),
4043            (
4044                besselk_seq_into,
4045                besselk,
4046                Scaling::Unscaled,
4047                |x| Complex::new(0.0, -x),
4048                0.1,
4049                ' ',
4050            ),
4051            (
4052                hankel2_seq_into,
4053                hankel2,
4054                Scaling::Unscaled,
4055                |x| Complex::new(0.0, x),
4056                1e-2,
4057                ' ',
4058            ),
4059            (
4060                hankel2_seq_into,
4061                hankel2_scaled,
4062                Scaling::Exponential,
4063                |x| Complex::new(0.0, x),
4064                1e-2,
4065                ' ',
4066            ),
4067            (
4068                hankel1_seq_into,
4069                hankel1_scaled,
4070                Scaling::Exponential,
4071                |x| Complex::new(0.0, -x),
4072                1e-2,
4073                ' ',
4074            ),
4075        ];
4076        let mut buf = [Complex::new(0.0, 0.0); 70];
4077        for (seq, single, scaling, zf, ratio, neg) in cases {
4078            for nu0 in [-7.3, -6.0, -40.5, 0.5, 2.0, 30.5] {
4079                for n in [1usize, 5, 40, 64, 70] {
4080                    // Not x = 1: H2_{±1.5}(i) = 0 exactly (K_{3/2}(1) = π·I_{3/2}(1))
4081                    // 1e-10 also reaches the single-value fallback of `fix_seq`
4082                    // (a chunk of Y overflows for ν0 = −40.5, n = 64 on −x)
4083                    for x in [1e-10, 0.01, 1.3, 7.5, 30.0] {
4084                        let z = zf(x);
4085                        let out = &mut buf[..n];
4086                        if seq(nu0, z, scaling, out).is_err() {
4087                            continue;
4088                        }
4089                        for (j, v) in out.iter().enumerate() {
4090                            let nu = nu0 + j as f64;
4091                            let Ok(s) = single(nu, z) else { continue };
4092                            let (sr, si) = (s.re.abs(), s.im.abs());
4093                            let (small, large) = (sr.min(si), sr.max(si));
4094                            let jy_ok = ratio == 1e-2
4095                                || x < nu.abs()
4096                                || neg_axis_selected(neg, nu, usize::from(sr >= si));
4097                            if jy_ok && small < 1e-3 * ratio * large && small > 0.0 {
4098                                let (gs, ss) = if sr < si { (v.re, s.re) } else { (v.im, s.im) };
4099                                let err = ((gs - ss) / ss).abs();
4100                                assert!(
4101                                    err < 1e-12,
4102                                    "nu={nu}, z={z}, n={n}: seq {v} vs single {s}"
4103                                );
4104                            } else if s.norm() > 0.0 {
4105                                let err = (v - s).norm() / s.norm();
4106                                assert!(
4107                                    err < 1e-10,
4108                                    "nu={nu}, z={z}, n={n}: seq {v} vs single {s}"
4109                                );
4110                                // A part recomputed as exactly zero (cos(νπ) = 0 at
4111                                // half-integers) must be zero in the sequence too
4112                                if jy_ok && small == 0.0 {
4113                                    let gs = if sr < si { v.re } else { v.im };
4114                                    assert!(
4115                                        gs == 0.0,
4116                                        "nu={nu}, z={z}, n={n}: seq {v} vs single {s}"
4117                                    );
4118                                }
4119                            }
4120                        }
4121                    }
4122                }
4123            }
4124        }
4125    }
4126}
4127
4128#[cfg(test)]
4129mod seq_tests {
4130    use super::*;
4131    use crate::utils::SEQ_WORKSPACE;
4132    use num_complex::Complex64;
4133
4134    /// Longest sequence used below; buffers are fixed arrays so that these
4135    /// tests also run without `std`.
4136    const MAX_N: usize = 128;
4137
4138    fn zeros<T: BesselFloat>() -> [Complex<T>; MAX_N] {
4139        [Complex::new(T::zero(), T::zero()); MAX_N]
4140    }
4141
4142    /// Power series path (ZSERI) where the top orders underflow after the
4143    /// near-underflow scaling was switched on: the lower orders retried after
4144    /// that must keep the scaling consistent (they came out multiplied by TOL,
4145    /// and so did every order below them through the recurrence).
4146    /// Whether a top order underflows is a rounding-level ZUCHK decision on
4147    /// the real axis, so scan several top orders and require at least one hit.
4148    #[test]
4149    fn seq_power_series_underflow_retry_keeps_scaling() {
4150        fn check<T: BesselFloat>(
4151            tops: core::ops::Range<i32>,
4152            ns: &[usize],
4153            z: Complex<T>,
4154            tol: f64,
4155        ) {
4156            for &n in ns {
4157                let mut hits = 0;
4158                for top in tops.clone() {
4159                    let nu = T::from_f64(f64::from(top) - (n - 1) as f64);
4160                    let mut buf = zeros::<T>();
4161                    let values = &mut buf[..n];
4162                    let info = besselj_seq_into(nu, z, Scaling::Unscaled, values).unwrap();
4163                    if info.underflow_count == 0 || values[0].norm() == T::zero() {
4164                        continue;
4165                    }
4166                    hits += 1;
4167                    for (j, &got) in values.iter().enumerate() {
4168                        if got.norm() == T::zero() {
4169                            continue;
4170                        }
4171                        let order = nu + T::from_f64(j as f64);
4172                        let single = besselj(order, z).unwrap();
4173                        let err = ((got - single).norm() / single.norm()).to_f64().unwrap();
4174                        // Loose bound: the scaled path near underflow differs from the
4175                        // single value by ~1e-13 (as in Fortran); the bug was a factor of TOL.
4176                        assert!(
4177                            err < tol,
4178                            "n={n} order={:?}: {got:?} vs {single:?}",
4179                            order.to_f64()
4180                        );
4181                    }
4182                }
4183                assert!(hits > 0, "n={n}: no case with a partial underflow");
4184            }
4185        }
4186        check(202..219, &[3, 40], Complex64::new(6.5, 0.0), 1e-10);
4187        check(36..48, &[3, 20], Complex::new(6.5f32, 0.0), 1e-4);
4188    }
4189
4190    /// With ν < 0, underflow can be at either end of the sequence: at large
4191    /// |orders| below zero or at the high orders above it. `underflow_count`
4192    /// must count every zero, as Amos does for ν ≥ 0 (including z = 0).
4193    #[test]
4194    fn seq_neg_underflow_count_counts_all_zeros() {
4195        fn check<T: BesselFloat>(radii: &[f64]) {
4196            let mut hits = 0;
4197            for nu in [-0.5, -3.0, -40.3, -100.0, -127.0] {
4198                for &r in radii {
4199                    for deg in [0.0, 60.0, 135.0, -100.0] {
4200                        let a = f64::to_radians(deg);
4201                        let z = Complex::new(T::from_f64(r * a.cos()), T::from_f64(r * a.sin()));
4202                        for s in [Scaling::Unscaled, Scaling::Exponential] {
4203                            for (name, seq, _, _) in seq_fns::<T>() {
4204                                let mut out = zeros::<T>();
4205                                let Ok(info) = seq(T::from_f64(nu), z, s, &mut out) else {
4206                                    continue;
4207                                };
4208                                let count = out.iter().filter(|v| v.norm() == T::zero()).count();
4209                                assert_eq!(
4210                                    info.underflow_count, count,
4211                                    "{name} ν={nu} r={r} {deg}°"
4212                                );
4213                                hits += usize::from(count > 0);
4214                            }
4215                        }
4216                    }
4217                }
4218            }
4219            assert!(hits > 0, "no case with underflow");
4220        }
4221        check::<f64>(&[0.1, 6.5, 40.0, 700.0]);
4222        check::<f32>(&[0.1, 6.5, 40.0, 90.0]);
4223
4224        // Trailing underflow after the orders cross zero (was reported as 0)
4225        let z = Complex64::new(0.1, 0.0);
4226        let mut out = zeros::<f64>();
4227        let info = besselj_seq_into(-0.5, z, Scaling::Unscaled, &mut out).unwrap();
4228        assert!(out[0].norm() > 0.0 && out[MAX_N - 1].norm() == 0.0);
4229        assert!(info.underflow_count > 0);
4230
4231        // z = 0: every order but 0 is zero, counted as for ν ≥ 0
4232        let z = Complex64::new(0.0, 0.0);
4233        for nu in [-2.0, 2.0] {
4234            for seq in [besselj_seq_into::<f64>, besseli_seq_into::<f64>] {
4235                let mut out = [Complex64::new(0.0, 0.0); 5];
4236                let info = seq(nu, z, Scaling::Unscaled, &mut out).unwrap();
4237                let expected = if nu < 0.0 { 4 } else { 5 };
4238                assert_eq!(info.underflow_count, expected, "ν={nu}");
4239            }
4240        }
4241    }
4242
4243    /// Above the half-precision threshold of |z| (~32767 for f64, ~2048 for
4244    /// f32), every path must report `Reduced`: positive ν, the negative
4245    /// lattices, crossings, and Y's chunked K values (n > SEQ_WORKSPACE).
4246    #[test]
4247    fn seq_reports_reduced_accuracy() {
4248        fn check<T: BesselFloat>(r: f64) {
4249            let z = Complex::new(T::from_f64(r * 0.6), T::from_f64(r * 0.8));
4250            for nu in [-80.5, -80.3, -3.0, -2.5, 0.5] {
4251                for n in [5, 70] {
4252                    for (name, seq, _, _) in seq_fns::<T>() {
4253                        let mut out = zeros::<T>();
4254                        let info =
4255                            seq(T::from_f64(nu), z, Scaling::Exponential, &mut out[..n]).unwrap();
4256                        assert_eq!(info.status, Accuracy::Reduced, "{name} ν={nu} n={n}");
4257                    }
4258                }
4259            }
4260        }
4261        check::<f64>(4.0e4);
4262        check::<f32>(3.0e3);
4263    }
4264
4265    // ── Edge: empty output ──
4266
4267    #[test]
4268    fn seq_into_empty_output_is_invalid_input() {
4269        let z = Complex64::new(1.5, 0.5);
4270        let s = Scaling::Unscaled;
4271        // Non-integer and integer ν take different code paths
4272        for nu in [-1.5, -2.0, 1.5] {
4273            for (name, seq, _, _) in seq_fns::<f64>() {
4274                assert_eq!(seq(nu, z, s, &mut []), Err(Error::InvalidInput), "{name}");
4275            }
4276        }
4277    }
4278
4279    // ── Seq/single consistency over a grid of regions ──
4280    //
4281    // Every element of a `_seq` result must match the single-value function at
4282    // the same order. The grid spans negative and positive orders, small to
4283    // near-overflow |z|, all four quadrants, and long sequences, so it reaches
4284    // the recurrence, rescaling, and analytic-continuation paths that only run
4285    // for n > 1.
4286
4287    type SeqIntoFn<T> = fn(T, Complex<T>, Scaling, &mut [Complex<T>]) -> Result<SeqInfo, Error>;
4288    type SingleFn<T> = fn(T, Complex<T>) -> Result<Complex<T>, Error>;
4289    /// (name, seq, single unscaled, single scaled)
4290    type SeqCase<T> = (&'static str, SeqIntoFn<T>, SingleFn<T>, SingleFn<T>);
4291
4292    fn seq_fns<T: BesselFloat>() -> [SeqCase<T>; 6] {
4293        [
4294            ("J", besselj_seq_into, besselj, besselj_scaled),
4295            ("Y", bessely_seq_into, bessely, bessely_scaled),
4296            ("I", besseli_seq_into, besseli, besseli_scaled),
4297            ("K", besselk_seq_into, besselk, besselk_scaled),
4298            ("H1", hankel1_seq_into, hankel1, hankel1_scaled),
4299            ("H2", hankel2_seq_into, hankel2, hankel2_scaled),
4300        ]
4301    }
4302
4303    /// Compare seq[j] with single(ν+j) for every (ν, |z|, arg z) on the grid.
4304    /// A sequence may fail as a whole only if some of its orders fail on their
4305    /// own (e.g. orders below the requested range must not cause an error),
4306    /// with one exception kept from the Fortran: K tests order max(last
4307    /// order, n), up to one order above the last for orders starting below 1,
4308    /// so such K, H, and Y sequences can report `Overflow` just below the
4309    /// limit (e.g. K from ν = 0.5 at z = 0.05 with n = 97). The converse does
4310    /// not hold either: Amos's overflow tests depend on the orders and length
4311    /// of the call, so a single value can report `Overflow` where a sequence
4312    /// that contains it succeeds (as in the Fortran, e.g. f32 K at ν = 10.5
4313    /// alone vs. from ν = 7.5 with n = 9, z = 90·e^{-2.6i}). Elements whose
4314    /// single value is an error, non-finite, or below 1e3 times the smallest
4315    /// normal number are skipped.
4316    fn check_seq_grid<T: BesselFloat>(
4317        nus: &[f64],
4318        radii: &[f64],
4319        args: &[f64],
4320        n: usize,
4321        tol: f64,
4322    ) {
4323        let floor = T::MACH_TINY * T::from_f64(1e3);
4324        for (name, seq, single, single_scaled) in seq_fns::<T>() {
4325            for scaling in [Scaling::Unscaled, Scaling::Exponential] {
4326                let single = if scaling == Scaling::Unscaled {
4327                    single
4328                } else {
4329                    single_scaled
4330                };
4331                for &nu in nus {
4332                    for &r in radii {
4333                        for &th in args {
4334                            let nu_t = T::from_f64(nu);
4335                            let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
4336                            let mut buf = zeros::<T>();
4337                            let values = &mut buf[..n];
4338                            match seq(nu_t, z, scaling, values) {
4339                                Ok(_) => {}
4340                                Err(e) => {
4341                                    let some_order_fails = (0..n)
4342                                        .any(|j| single(nu_t + T::from_f64(j as f64), z).is_err());
4343                                    assert!(
4344                                        some_order_fails,
4345                                        "{name} {scaling:?}: nu={nu}, z={z:?}, n={n}: seq \
4346                                         failed with {e:?} but every single value succeeds"
4347                                    );
4348                                    continue;
4349                                }
4350                            }
4351                            for (j, &w) in values.iter().enumerate() {
4352                                let order = nu_t + T::from_f64(j as f64);
4353                                let Ok(v) = single(order, z) else {
4354                                    continue;
4355                                };
4356                                if !(v.norm() > floor && v.norm().is_finite()) {
4357                                    continue;
4358                                }
4359                                let err = ((w - v).norm() / v.norm()).to_f64().unwrap();
4360                                assert!(
4361                                    err < tol,
4362                                    "{name} {scaling:?}: nu={nu}, z={z:?}, n={n}, j={j}: \
4363                                     seq={w:?}, single={v:?}, rel_err={err:e}"
4364                                );
4365                            }
4366                        }
4367                    }
4368                }
4369            }
4370        }
4371    }
4372
4373    /// Angles covering all four quadrants, the real and imaginary axes, and
4374    /// both sides of the |Im z| = √3·|Re z| region boundary.
4375    const ARGS: [f64; 8] = [
4376        0.0,
4377        0.5,
4378        1.2,
4379        core::f64::consts::FRAC_PI_2,
4380        2.2,
4381        3.0,
4382        -1.0,
4383        -2.6,
4384    ];
4385
4386    /// Recurrence paths in f64. The seq values come from recurrence while the
4387    /// single values come from direct expansions, so their ~1e-14 errors do not
4388    /// cancel (observed up to ~6e-12 for n = 100); a misplaced or mis-scaled
4389    /// element gives O(1e-3) error or worse.
4390    const TOL_GRID_F64: f64 = 1e-10;
4391
4392    #[test]
4393    fn seq_matches_single_grid_f64() {
4394        // Orders: all-negative / crossing (integer and not), small, and beyond
4395        // FNUL (≈ 86) so the uniform expansions recur. Radii include values near
4396        // the ELIM/ALIM scaling thresholds (≈ 665–700) and beyond, where low
4397        // orders overflow but ν = -200 does not. ν = 575 at |z| = 360 reaches
4398        // scaled Y with 2|Im z| > ELIM, where the K term still dominates. -0.0
4399        // is an edge case of the sign dispatch; n = 1 is the one-element
4400        // lattice edge case. For n = 9, ν = -3 / -4 / -7 cover an integer
4401        // crossing whose largest |order| is positive / both / negative.
4402        for n in [1, 9] {
4403            check_seq_grid::<f64>(
4404                &[
4405                    -200.5, -200.0, -20.0, -12.3, -7.0, -5.3, -4.0, -3.0, -2.5, -0.0, 0.0, 0.4,
4406                    13.5, 46.2, 81.7, 121.3, 575.0,
4407                ],
4408                &[
4409                    0.3, 3.0, 17.0, 45.0, 120.0, 330.0, 360.0, 666.0, 698.0, 705.0, 709.0,
4410                ],
4411                &ARGS,
4412                n,
4413                TOL_GRID_F64,
4414            );
4415        }
4416    }
4417
4418    #[test]
4419    fn seq_matches_single_grid_f64_long() {
4420        // n = 100 with small ν: ν + n − 1 exceeds FNUL while ν itself does not.
4421        // Negative non-integer ν spans several stack chunks of the secondary
4422        // function (all negative, and crossing zero with more negative orders
4423        // than a chunk holds).
4424        check_seq_grid::<f64>(
4425            &[-80.3, -40.3, 0.5, 3.3],
4426            &[2.2, 25.0, 90.0],
4427            &ARGS,
4428            100,
4429            TOL_GRID_F64,
4430        );
4431    }
4432
4433    /// f32 hits the scaling thresholds at |values| ≈ 1e28, so moderate |z|
4434    /// exercises the rescaling branches. Observed up to ~1.4e-4 on this grid;
4435    /// misplaced or mis-scaled elements give O(1) error or worse. (Off this grid,
4436    /// near the turning point ν ≈ |z| ≈ FNUL, Amos's single-precision algorithm
4437    /// itself loses up to ~2e-2, so random points would need a looser bound.)
4438    const TOL_GRID_F32: f64 = 1e-3;
4439
4440    #[test]
4441    fn seq_matches_single_grid_f32() {
4442        // At |z| = 90, low orders overflow but ν = -60 does not. ν = 70 at
4443        // |z| = 45 reaches scaled Y with 2|Im z| > ELIM (K term dominant).
4444        check_seq_grid::<f32>(
4445            &[
4446                -60.0, -25.5, -9.0, -4.6, -0.0, 0.0, 0.4, 7.5, 21.2, 29.9, 46.3, 70.0,
4447            ],
4448            &[0.3, 3.0, 17.0, 45.0, 70.0, 90.0],
4449            &ARGS,
4450            9,
4451            TOL_GRID_F32,
4452        );
4453    }
4454
4455    // ── Three-term recurrence across ν = 0 ──
4456    //
4457    // Negative orders come from reflection formulas shared by the single-value
4458    // and `_seq` functions, so comparing the two cannot catch an error in a
4459    // formula. The recurrence can: across ν = 0 it links reflected values to
4460    // directly computed ones. Every scaling factor is independent of the order,
4461    // so scaled values satisfy the same recurrence.
4462
4463    /// Check C(ν-1) + s·C(ν+1) = t·(2ν/z)·C(ν) on consecutive `_seq` elements,
4464    /// with (s, t) = (1, 1) for J, Y, H1, H2, (-1, 1) for I, and (-1, -1) for K
4465    /// (DLMF 10.6.1, 10.29.1). The residual is relative to the largest term.
4466    fn check_recurrence_grid<T: BesselFloat>(
4467        nus: &[f64],
4468        radii: &[f64],
4469        args: &[f64],
4470        n: usize,
4471        tol: f64,
4472    ) {
4473        for (name, seq, _, _) in seq_fns::<T>() {
4474            let (s, t) = match name {
4475                "I" => (-1.0, 1.0),
4476                "K" => (-1.0, -1.0),
4477                _ => (1.0, 1.0),
4478            };
4479            let (s, t) = (T::from_f64(s), T::from_f64(t));
4480            for scaling in [Scaling::Unscaled, Scaling::Exponential] {
4481                for &nu in nus {
4482                    for &r in radii {
4483                        for &th in args {
4484                            let nu_t = T::from_f64(nu);
4485                            let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
4486                            let mut buf = zeros::<T>();
4487                            let values = &mut buf[..n];
4488                            if seq(nu_t, z, scaling, values).is_err() {
4489                                continue;
4490                            }
4491                            for (j, w) in values.windows(3).enumerate() {
4492                                // Underflowed (zeroed) or overflowed elements break the recurrence
4493                                if w.iter()
4494                                    .any(|v| v.norm() == T::zero() || !v.norm().is_finite())
4495                                {
4496                                    continue;
4497                                }
4498                                let order = nu_t + T::from_f64((j + 1) as f64);
4499                                let lhs = w[0] + w[2] * s;
4500                                let rhs = w[1] * (z.inv() * (T::from_f64(2.0) * order * t));
4501                                let mag = w[0].norm().max(w[2].norm()).max(rhs.norm());
4502                                let err = ((lhs - rhs).norm() / mag).to_f64().unwrap();
4503                                assert!(
4504                                    err < tol,
4505                                    "{name} {scaling:?}: nu={nu}, z={z:?}, order={:?}: \
4506                                     recurrence residual {err:e}",
4507                                    order.to_f64()
4508                                );
4509                            }
4510                        }
4511                    }
4512                }
4513            }
4514        }
4515    }
4516
4517    #[test]
4518    fn seq_recurrence_across_zero_f64() {
4519        // Non-integer (generic and half-integer) and integer orders crossing
4520        // zero, plus all-negative sequences.
4521        check_recurrence_grid::<f64>(
4522            &[-12.3, -5.3, -4.0, -3.7, -2.5, -0.3],
4523            &[0.3, 3.0, 17.0, 45.0],
4524            &ARGS,
4525            9,
4526            1e-11, // observed up to ~1.3e-13
4527        );
4528    }
4529
4530    #[test]
4531    fn seq_recurrence_across_zero_f32() {
4532        check_recurrence_grid::<f32>(
4533            &[-12.3, -5.3, -4.0, -3.7, -2.5, -0.3],
4534            &[0.3, 3.0, 17.0, 45.0],
4535            &ARGS,
4536            9,
4537            1e-4, // observed up to ~1e-5
4538        );
4539    }
4540
4541    // ── Chunked workspace ──
4542    //
4543    // Y's K values and the secondary function of the negative non-integer
4544    // reflection formulas go to a workspace, in chunks of its length (Y, and
4545    // I with non-integer ν < 0) or of half of it (J and Y with non-integer
4546    // ν < 0, except half-integer ν, which need one function). A workspace
4547    // that covers the whole
4548    // sequence (as `_seq` allocates) gives one Amos call per function, so
4549    // compare short workspaces against it. Amos's tests depend on the orders
4550    // and length of each call, so a chunked sequence may fail only where some
4551    // single value fails, as above.
4552
4553    type ImplFn<T> = for<'a> fn(
4554        T,
4555        Complex<T>,
4556        Scaling,
4557        &mut [Complex<T>],
4558        Workspace<'a, T>,
4559    ) -> Result<SeqInfo, Error>;
4560
4561    /// (name, implementation, single unscaled, single scaled)
4562    type ImplCase<T> = (&'static str, ImplFn<T>, SingleFn<T>, SingleFn<T>);
4563
4564    fn impl_fns<T: BesselFloat>() -> [ImplCase<T>; 3] {
4565        [
4566            ("J", besselj_seq_impl, besselj, besselj_scaled),
4567            ("Y", bessely_seq_impl, bessely, bessely_scaled),
4568            ("I", besseli_seq_impl, besseli, besseli_scaled),
4569        ]
4570    }
4571
4572    /// Bitwise equality (exact through the lossless conversion to f64).
4573    fn same_bits<T: BesselFloat>(x: Complex<T>, y: Complex<T>) -> bool {
4574        let bits = |v: T| v.to_f64().unwrap().to_bits();
4575        bits(x.re) == bits(y.re) && bits(x.im) == bits(y.im)
4576    }
4577
4578    fn check_chunk_invariance<T: BesselFloat>(
4579        nus: &[f64],
4580        radii: &[f64],
4581        ns: &[usize],
4582        ws_len: usize,
4583        tol: f64,
4584    ) {
4585        let floor = T::MACH_TINY * T::from_f64(1e3);
4586        let czero = Complex::new(T::zero(), T::zero());
4587        for (name, f, single, single_scaled) in impl_fns::<T>() {
4588            for scaling in [Scaling::Unscaled, Scaling::Exponential] {
4589                let single = if scaling == Scaling::Unscaled {
4590                    single
4591                } else {
4592                    single_scaled
4593                };
4594                for &nu in nus {
4595                    for &r in radii {
4596                        for &th in &ARGS {
4597                            for &n in ns {
4598                                let nu_t = T::from_f64(nu);
4599                                let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
4600                                let (mut a, mut b) = (zeros::<T>(), zeros::<T>());
4601                                let (a, b) = (&mut a[..n], &mut b[..n]);
4602                                let mut ws_whole = [czero; 2 * MAX_N];
4603                                let mut ws_short = [czero; 2 * MAX_N];
4604                                let whole = f(nu_t, z, scaling, a, Workspace::Slice(&mut ws_whole));
4605                                let chunked = f(
4606                                    nu_t,
4607                                    z,
4608                                    scaling,
4609                                    b,
4610                                    Workspace::Slice(&mut ws_short[..ws_len]),
4611                                );
4612                                let some_single_fails = || {
4613                                    (0..n).any(|j| single(nu_t + T::from_f64(j as f64), z).is_err())
4614                                };
4615                                let (info_a, info_b) = match (whole, chunked) {
4616                                    (Ok(ia), Ok(ib)) => (ia, ib),
4617                                    (Ok(_), Err(e)) => {
4618                                        assert!(
4619                                            some_single_fails(),
4620                                            "{name} {scaling:?} ws={ws_len}: nu={nu}, z={z:?}, \
4621                                             n={n}: chunked failed with {e:?}, whole call and \
4622                                             every single value succeed"
4623                                        );
4624                                        continue;
4625                                    }
4626                                    // Chunks can pass Amos's tests where the whole call
4627                                    // fails (e.g. K's test of max(last order, n)); the
4628                                    // values must then match the single values.
4629                                    (Err(_), Ok(_)) => {
4630                                        for (j, &y) in b.iter().enumerate() {
4631                                            let Ok(v) = single(nu_t + T::from_f64(j as f64), z)
4632                                            else {
4633                                                continue;
4634                                            };
4635                                            if !(v.norm() > floor && v.norm().is_finite()) {
4636                                                continue;
4637                                            }
4638                                            let err = ((y - v).norm() / v.norm()).to_f64().unwrap();
4639                                            assert!(
4640                                                err < tol,
4641                                                "{name} {scaling:?} ws={ws_len}: nu={nu}, \
4642                                                 z={z:?}, n={n}, j={j}: chunked={y:?}, \
4643                                                 single={v:?}"
4644                                            );
4645                                        }
4646                                        continue;
4647                                    }
4648                                    (Err(_), Err(_)) => continue,
4649                                };
4650                                if 2 * n <= ws_len {
4651                                    // One chunk: the same calls as the whole sequence
4652                                    assert_eq!(
4653                                        info_a, info_b,
4654                                        "{name} {scaling:?} ws={ws_len}: nu={nu}, n={n}"
4655                                    );
4656                                    for (&x, &y) in a.iter().zip(b.iter()) {
4657                                        assert!(
4658                                            same_bits(x, y),
4659                                            "{name} {scaling:?} ws={ws_len}: nu={nu}, z={z:?}, \
4660                                             n={n}: {x:?} vs {y:?}"
4661                                        );
4662                                    }
4663                                    continue;
4664                                }
4665                                // The underflow count is decided per call, so only the
4666                                // status must match
4667                                assert_eq!(
4668                                    info_a.status, info_b.status,
4669                                    "{name} {scaling:?} ws={ws_len}: nu={nu}, n={n}"
4670                                );
4671                                for (j, (&x, &y)) in a.iter().zip(b.iter()).enumerate() {
4672                                    if !(x.norm() > floor && x.norm().is_finite()) {
4673                                        continue;
4674                                    }
4675                                    let err = ((x - y).norm() / x.norm()).to_f64().unwrap();
4676                                    assert!(
4677                                        err < tol,
4678                                        "{name} {scaling:?} ws={ws_len}: nu={nu}, z={z:?}, \
4679                                         n={n}, j={j}: whole={x:?}, chunked={y:?}, \
4680                                         rel_err={err:e}"
4681                                    );
4682                                }
4683                            }
4684                        }
4685                    }
4686                }
4687            }
4688        }
4689    }
4690
4691    /// Workspace lengths: chunks of 1, 2, 7 orders for J and Y with non-integer,
4692    /// non-half-integer ν < 0 (half the workspace) and of 2, 4, 14 otherwise,
4693    /// and the stack workspace.
4694    const WS_LENS: [usize; 4] = [2, 4, 14, SEQ_WORKSPACE];
4695
4696    #[test]
4697    fn seq_chunk_size_invariance_f64() {
4698        // Negative non-integer ν (all negative, and crossing zero) chunk the
4699        // secondary function; positive ν and negative integer ν (both mirror
4700        // branches of a crossing: n = 100 from −40 and from −70) chunk the K
4701        // values inside Y. Radii reach the ELIM/ALIM thresholds. With the
4702        // stack workspace, n = 32 and 33 end at a chunk boundary or one past it
4703        // for J and Y with non-integer ν < 0, and n = 9 is a single chunk.
4704        let nus = [
4705            -80.3, -70.0, -40.5, -40.3, -40.0, -12.3, -0.7, 0.4, 3.3, 81.7,
4706        ];
4707        let radii = [0.3, 3.0, 17.0, 45.0, 120.0, 666.0, 698.0, 705.0];
4708        let ns = [9, 32, 33, 65, 100];
4709        for ws_len in WS_LENS {
4710            check_chunk_invariance::<f64>(&nus, &radii, &ns, ws_len, TOL_CHUNK_F64);
4711        }
4712    }
4713
4714    #[test]
4715    fn seq_chunk_size_invariance_f32() {
4716        // Thresholds sit at |z| ≈ 34–100 in f32; z = 90·e^{-2.6i} is where K's
4717        // overflow test depends on the call (see `check_seq_grid`).
4718        let nus = [-70.0, -40.3, -40.0, -15.5, -4.6, 0.4, 7.5, 29.9];
4719        let radii = [3.0, 17.0, 34.0, 45.0, 70.0, 90.0, 100.0];
4720        let ns = [9, 32, 33, 65, 100];
4721        for ws_len in WS_LENS {
4722            check_chunk_invariance::<f32>(&nus, &radii, &ns, ws_len, TOL_CHUNK_F32);
4723        }
4724    }
4725
4726    /// Chunks restart Amos's algorithms, so their values differ from the whole
4727    /// call as single values do, plus where Amos's own error is largest: on
4728    /// these grids up to ~1.4e-9 in f64 (Y near |z| = 705 on the real axis,
4729    /// where both differ from mpmath by ~3e-9) and ~3.9e-3 in f32 (near the
4730    /// turning point). Off the grids, cancellation in the reflection formulas
4731    /// can reach ~1.3e-8 (J, ν = −250.7, z = 698, where |J| ≈ 5e-7 comes from
4732    /// J and Y terms ≈ 3e-2); the whole call itself shifts by ~3e-9 there with
4733    /// n. A misplaced or mis-scaled chunk gives O(1) error.
4734    const TOL_CHUNK_F64: f64 = 1e-8;
4735    const TOL_CHUNK_F32: f64 = 2e-2;
4736
4737    // ── Output buffer contents ──
4738
4739    #[test]
4740    fn seq_into_ignores_initial_out_contents() {
4741        // Every path must overwrite `out`; NaN left behind would show up
4742        let z_list = [Complex64::new(1.5, 0.5), Complex64::new(-30.0, 20.0)];
4743        for (name, seq, _, _) in seq_fns::<f64>() {
4744            for nu in [-40.3, -7.0, -2.5, 0.4, 3.0] {
4745                for z in z_list {
4746                    for n in [1, 9, 40] {
4747                        let mut a = [Complex64::new(0.0, 0.0); 40];
4748                        let mut b = [Complex64::new(f64::NAN, f64::NAN); 40];
4749                        for scaling in [Scaling::Unscaled, Scaling::Exponential] {
4750                            let ra = seq(nu, z, scaling, &mut a[..n]);
4751                            let rb = seq(nu, z, scaling, &mut b[..n]);
4752                            assert_eq!(ra, rb, "{name}: nu={nu}, z={z:?}, n={n}");
4753                            if ra.is_err() {
4754                                continue;
4755                            }
4756                            for (&x, &y) in a[..n].iter().zip(b[..n].iter()) {
4757                                assert!(
4758                                    same_bits(x, y),
4759                                    "{name}: nu={nu}, z={z:?}, n={n}: {x:?} vs {y:?}"
4760                                );
4761                            }
4762                        }
4763                    }
4764                }
4765            }
4766        }
4767    }
4768}
4769
4770// ── Allocating `_seq` wrappers ──
4771
4772#[cfg(all(test, feature = "std"))]
4773mod seq_vec_tests {
4774    use super::*;
4775    use num_complex::Complex64;
4776
4777    /// `_seq` gives every function a heap workspace of the full length, so each
4778    /// is one Amos call (as in 0.4), unlike the stack chunks of `_seq_into`.
4779    /// These long sequences span several stack chunks.
4780    #[test]
4781    fn seq_computes_each_function_in_one_call() {
4782        type Seq = fn(f64, Complex64, usize, Scaling) -> Result<BesselResult<f64>, Error>;
4783        type Impl = for<'a> fn(
4784            f64,
4785            Complex64,
4786            Scaling,
4787            &mut [Complex64],
4788            Workspace<'a, f64>,
4789        ) -> Result<SeqInfo, Error>;
4790        let cases: [(Seq, Impl, f64, Complex64); 6] = [
4791            (
4792                bessely_seq,
4793                bessely_seq_impl,
4794                0.4,
4795                Complex64::from_polar(25.0, 0.5),
4796            ),
4797            (
4798                bessely_seq,
4799                bessely_seq_impl,
4800                -40.3,
4801                Complex64::from_polar(17.0, -1.0),
4802            ),
4803            (
4804                besselj_seq,
4805                besselj_seq_impl,
4806                -80.3,
4807                Complex64::from_polar(45.0, 3.0),
4808            ),
4809            (
4810                besseli_seq,
4811                besseli_seq_impl,
4812                -40.3,
4813                Complex64::from_polar(17.0, 2.2),
4814            ),
4815            // Half-integer |ν|: one function for the negative orders
4816            (
4817                besselj_seq,
4818                besselj_seq_impl,
4819                -80.5,
4820                Complex64::from_polar(45.0, 3.0),
4821            ),
4822            (
4823                bessely_seq,
4824                bessely_seq_impl,
4825                -80.5,
4826                Complex64::from_polar(17.0, -1.0),
4827            ),
4828        ];
4829        const N: usize = 100;
4830        for (seq, imp, nu, z) in cases {
4831            let r = seq(nu, z, N, Scaling::Unscaled).unwrap();
4832            let mut out = [Complex64::new(0.0, 0.0); N];
4833            let mut ws = [Complex64::new(0.0, 0.0); 2 * N];
4834            let info = imp(
4835                nu,
4836                z,
4837                Scaling::Unscaled,
4838                &mut out,
4839                Workspace::Slice(&mut ws),
4840            )
4841            .unwrap();
4842            assert_eq!(
4843                (r.underflow_count, r.status),
4844                (info.underflow_count, info.status)
4845            );
4846            for (j, (x, y)) in r.values.iter().zip(out.iter()).enumerate() {
4847                assert!(
4848                    x.re.to_bits() == y.re.to_bits() && x.im.to_bits() == y.im.to_bits(),
4849                    "nu={nu}, z={z:?}, j={j}: {x:?} vs {y:?}"
4850                );
4851            }
4852        }
4853    }
4854
4855    #[test]
4856    fn seq_matches_seq_into() {
4857        type Pair = (
4858            fn(f64, Complex64, usize, Scaling) -> Result<BesselResult<f64>, Error>,
4859            fn(f64, Complex64, Scaling, &mut [Complex64]) -> Result<SeqInfo, Error>,
4860        );
4861        let pairs: [Pair; 6] = [
4862            (besselj_seq, besselj_seq_into),
4863            (bessely_seq, bessely_seq_into),
4864            (besseli_seq, besseli_seq_into),
4865            (besselk_seq, besselk_seq_into),
4866            (hankel1_seq, hankel1_seq_into),
4867            (hankel2_seq, hankel2_seq_into),
4868        ];
4869        let z = Complex64::new(1.5, -0.5);
4870        for (seq, seq_into) in pairs {
4871            for nu in [-5.3, -2.0, 0.0, 1.7] {
4872                for n in [0, 1, 6] {
4873                    let mut out = [Complex64::new(0.0, 0.0); 6];
4874                    let expected = seq_into(nu, z, Scaling::Unscaled, &mut out[..n]);
4875                    match seq(nu, z, n, Scaling::Unscaled) {
4876                        Ok(r) => {
4877                            let info = expected.unwrap();
4878                            assert_eq!(r.values, &out[..n]);
4879                            assert_eq!(r.underflow_count, info.underflow_count);
4880                            assert_eq!(r.status, info.status);
4881                        }
4882                        Err(e) => assert_eq!(Err(e), expected),
4883                    }
4884                }
4885            }
4886        }
4887    }
4888}