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, Bi
7//! - **Consecutive orders** — `_seq` variants return ν, ν+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` support** — 3-tier: bare `no_std`, `alloc`, `std` (default)
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)` | 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)` | 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)` | 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)` | 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)` | 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)` | 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//! ## Exponential scaling
50//!
51//! Each `_scaled` variant multiplies the result by an exponential factor that
52//! cancels the asymptotic growth (or decay), keeping values in a representable
53//! floating-point range. The exact factor is listed in the "Scaled variant
54//! returns" column above. Use scaled functions whenever |z| or |Im(z)| is
55//! large enough that the unscaled result would overflow or underflow.
56//!
57//! # Negative orders
58//!
59//! All functions accept any real order, including negative values.
60//! DLMF reflection formulas are applied automatically:
61//!
62//! - **J**: J<sub>−ν</sub>(z) = cos(νπ) J<sub>ν</sub>(z) − sin(νπ) Y<sub>ν</sub>(z) (DLMF 10.2.3)
63//! - **Y**: Y<sub>−ν</sub>(z) = sin(νπ) J<sub>ν</sub>(z) + cos(νπ) Y<sub>ν</sub>(z) (DLMF 10.2.3)
64//! - **I**: I<sub>−ν</sub>(z) = I<sub>ν</sub>(z) + (2/π) sin(νπ) K<sub>ν</sub>(z) (DLMF 10.27.2)
65//! - **K**: K<sub>−ν</sub>(z) = K<sub>ν</sub>(z) (DLMF 10.27.3)
66//! - **H<sup>(1)</sup>**: H<sup>(1)</sup><sub>−ν</sub>(z) = exp(νπi) H<sup>(1)</sup><sub>ν</sub>(z) (DLMF 10.4.6)
67//! - **H<sup>(2)</sup>**: H<sup>(2)</sup><sub>−ν</sub>(z) = exp(−νπi) H<sup>(2)</sup><sub>ν</sub>(z) (DLMF 10.4.6)
68//!
69//! For integer orders, simplified identities are used (e.g., J<sub>−n</sub>(z) = (−1)<sup>n</sup> J<sub>n</sub>(z)).
70//!
71//! # Function variants
72//!
73//! ## Consecutive orders
74//!
75//! The `_seq` variants ([`besselj_seq`], [`besselk_seq`], …) compute values at
76//! consecutive orders ν, ν+1, …, ν+n−1 in a single call and return
77//! a [`BesselResult`] that includes an [`Accuracy`] field.
78//! The `_raw` Airy variants ([`airy_raw`], [`biry_raw`], …) similarly return
79//! an [`AiryResult`] with [`Accuracy`].
80//! All other functions return only the computed value without [`Accuracy`].
81//!
82//! **[`Accuracy`]:**
83//!
84//! | Status | Meaning |
85//! |--------|---------|
86//! | [`Normal`](Accuracy::Normal) | Full machine precision |
87//! | [`Reduced`](Accuracy::Reduced) | More than half of significant digits may be lost |
88//!
89//! [`Reduced`](Accuracy::Reduced) is extremely rare in practice. SciPy's Bessel wrappers also silently
90//! discard the equivalent Amos IERR=3 flag by default.
91//!
92//! To check accuracy status, use a `_seq` function (Bessel) or a `_raw` function (Airy):
93//!
94//! ```
95//! # #[cfg(feature = "alloc")] {
96//! use complex_bessel::*;
97//! use num_complex::Complex;
98//!
99//! let z = Complex::new(1.0, 2.0);
100//! let result = besselk_seq(0.0, z, 1, Scaling::Unscaled).unwrap();
101//! assert!(matches!(result.status, Accuracy::Normal));
102//!
103//! let result = airy_raw(z, Scaling::Unscaled).unwrap();
104//! assert!(matches!(result.status, Accuracy::Normal));
105//! # }
106//! ```
107//!
108//! # Error handling
109//!
110//! All functions return `Result<_, Error>`. The four error variants are:
111//!
112//! | Variant | Cause |
113//! |---------|-------|
114//! | [`InvalidInput`](Error::InvalidInput) | non-finite z or ν, z = 0 for K/Y/H, n < 1 |
115//! | [`Overflow`](Error::Overflow) | \|z\| or \|ν\| too large (or \|z\| too small) for finite result |
116//! | [`TotalPrecisionLoss`](Error::TotalPrecisionLoss) | \|z\| or \|ν\| too large for meaningful computation |
117//! | [`ConvergenceFailure`](Error::ConvergenceFailure) | Internal algorithm did not converge |
118//!
119//! # `no_std` support
120//!
121//! | Cargo features | Available API |
122//! |---------------|---------------|
123//! | `default-features = false` | 24 single-value functions |
124//! | `features = ["alloc"]` | full API (+ 6 `_seq` variants) |
125//! | `features = ["std"]` (default) | full API, with platform-native math |
126//!
127//! The 24 single-value functions include 12 Bessel (J/Y/I/K/H<sup>(1)</sup>/H<sup>(2)</sup> × unscaled/scaled)
128//! and 12 Airy (Ai/Ai'/Bi/Bi' × unscaled/scaled/raw).
129//!
130//! ```toml
131//! # Bare no_std — no allocator needed:
132//! complex-bessel = { version = "0.3", default-features = false }
133//!
134//! # no_std + alloc — full API:
135//! complex-bessel = { version = "0.3", default-features = false, features = ["alloc"] }
136//! ```
137
138#![warn(missing_docs)]
139#![no_std]
140
141#[cfg(feature = "alloc")]
142extern crate alloc;
143#[cfg(feature = "std")]
144extern crate std;
145
146pub(crate) mod airy;
147pub(crate) mod algo;
148pub(crate) mod besh;
149pub(crate) mod besi;
150pub(crate) mod besj;
151pub(crate) mod besk;
152pub(crate) mod besy;
153pub mod machine;
154pub mod types;
155pub(crate) mod utils;
156
157pub use machine::BesselFloat;
158#[cfg(feature = "alloc")]
159pub use types::BesselResult;
160pub use types::{Accuracy, AiryResult, Error, Scaling};
161
162use num_complex::Complex;
163use types::{AiryDerivative, HankelKind};
164
165// ── Helper: integer order detection ──
166
167/// Check if `nu` is a non-negative integer. Returns `Some(n)` if so.
168#[inline]
169fn as_integer<T: BesselFloat>(nu: T) -> Option<i64> {
170    if nu == nu.floor() {
171        // Safe conversion: orders beyond i64 range are not practical
172        nu.to_i64()
173    } else {
174        None
175    }
176}
177
178// ── Element-wise reflection helpers (shared by single-value and _seq) ──
179
180/// J_{-ν}(z) = cos(νπ)·J_ν(z) − sin(νπ)·Y_ν(z)  (DLMF 10.2.3)
181#[inline]
182fn reflect_j_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
183    j * utils::cospi(order) - y * utils::sinpi(order)
184}
185
186/// Y_{-ν}(z) = sin(νπ)·J_ν(z) + cos(νπ)·Y_ν(z)  (DLMF 10.2.3)
187#[inline]
188fn reflect_y_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
189    j * utils::sinpi(order) + y * utils::cospi(order)
190}
191
192/// I_{-ν}(z) = I_ν(z) + (2/π)·sin(νπ)·K_ν(z)  (DLMF 10.27.2)
193/// `k_val` must already have scaling correction applied by the caller.
194#[inline]
195fn reflect_i_element<T: BesselFloat>(order: T, i_val: Complex<T>, k_val: Complex<T>) -> Complex<T> {
196    let pi = T::from_f64(core::f64::consts::PI);
197    let two = T::from_f64(2.0);
198    utils::mul_add_scalar(k_val, two / pi * utils::sinpi(order), i_val)
199}
200
201/// H^(m)_{-ν}(z) = exp(±νπi)·H^(m)_ν(z)  (DLMF 10.4.6/7)
202#[inline]
203fn reflect_h_element<T: BesselFloat>(order: T, kind: HankelKind, h: Complex<T>) -> Complex<T> {
204    let cos_nu_pi = utils::cospi(order);
205    let sin_nu_pi = utils::sinpi(order);
206    let rotation = match kind {
207        HankelKind::First => Complex::new(cos_nu_pi, sin_nu_pi),
208        HankelKind::Second => Complex::new(cos_nu_pi, -sin_nu_pi),
209    };
210    h * rotation
211}
212
213/// Convert K scaling to I scaling for the reflection formula.
214/// I_scaled = exp(-|Re(z)|)·I, K_scaled = exp(z)·K.
215/// factor = exp(-|Re(z)|) / exp(z) = exp(-i·Im(z)) · [exp(-2·Re(z)) if Re(z)>0]
216#[inline]
217fn k_to_i_scaling_correction<T: BesselFloat>(z: Complex<T>, k_val: Complex<T>) -> Complex<T> {
218    let two = T::from_f64(2.0);
219    let (sin_a, cos_a) = (-z.im).sin_cos();
220    let mut result = k_val * Complex::new(cos_a, sin_a);
221    if z.re > T::zero() {
222        result = result * (-two * z.re).exp();
223    }
224    result
225}
226
227/// (-1)^n sign factor for integer order reflection.
228#[inline]
229fn integer_sign<T: BesselFloat>(n: i64) -> T {
230    if n % 2 == 0 { T::one() } else { -T::one() }
231}
232
233// ── Internal: compute with given scaling for negative order support ──
234
235#[inline]
236fn besselj_internal<T: BesselFloat>(
237    nu: T,
238    z: Complex<T>,
239    scaling: Scaling,
240) -> Result<Complex<T>, Error> {
241    let zero = T::zero();
242    let czero = Complex::new(zero, zero);
243    if nu >= zero {
244        let mut y = [czero];
245        besj::zbesj(z, nu, scaling, &mut y)?;
246        return Ok(y[0]);
247    }
248
249    let abs_nu = nu.abs();
250
251    // Integer shortcut: J_{-n}(z) = (-1)^n * J_n(z)
252    if let Some(n) = as_integer(abs_nu) {
253        let mut y = [czero];
254        besj::zbesj(z, abs_nu, scaling, &mut y)?;
255        return Ok(y[0] * integer_sign::<T>(n));
256    }
257
258    // General case: need both J and Y at positive |ν|
259    let mut j_buf = [czero];
260    let mut y_buf = [czero];
261    besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
262    besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
263
264    Ok(reflect_j_element(abs_nu, j_buf[0], y_buf[0]))
265}
266
267#[inline]
268fn bessely_internal<T: BesselFloat>(
269    nu: T,
270    z: Complex<T>,
271    scaling: Scaling,
272) -> Result<Complex<T>, Error> {
273    let zero = T::zero();
274    let czero = Complex::new(zero, zero);
275    if nu >= zero {
276        let mut y = [czero];
277        besy::zbesy(z, nu, scaling, &mut y)?;
278        return Ok(y[0]);
279    }
280
281    let abs_nu = nu.abs();
282
283    // Integer shortcut: Y_{-n}(z) = (-1)^n * Y_n(z)
284    if let Some(n) = as_integer(abs_nu) {
285        let mut y = [czero];
286        besy::zbesy(z, abs_nu, scaling, &mut y)?;
287        return Ok(y[0] * integer_sign::<T>(n));
288    }
289
290    // General case: need both J and Y at positive |ν|
291    let mut j_buf = [czero];
292    let mut y_buf = [czero];
293    besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
294    besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
295
296    Ok(reflect_y_element(abs_nu, j_buf[0], y_buf[0]))
297}
298
299#[inline]
300fn besseli_internal<T: BesselFloat>(
301    nu: T,
302    z: Complex<T>,
303    scaling: Scaling,
304) -> Result<Complex<T>, Error> {
305    let zero = T::zero();
306    let czero = Complex::new(zero, zero);
307    if nu >= zero {
308        let mut y = [czero];
309        besi::zbesi(z, nu, scaling, &mut y)?;
310        return Ok(y[0]);
311    }
312
313    let abs_nu = nu.abs();
314
315    // Integer shortcut: I_{-n}(z) = I_n(z)
316    if as_integer(abs_nu).is_some() {
317        let mut y = [czero];
318        besi::zbesi(z, abs_nu, scaling, &mut y)?;
319        return Ok(y[0]);
320    }
321
322    // General case: need both I and K at positive |ν|
323    let mut i_buf = [czero];
324    let mut k_buf = [czero];
325    besi::zbesi(z, abs_nu, scaling, &mut i_buf)?;
326    besk::zbesk(z, abs_nu, scaling, &mut k_buf)?;
327
328    let mut k_val = k_buf[0];
329    if scaling == Scaling::Exponential {
330        k_val = k_to_i_scaling_correction(z, k_val);
331    }
332
333    Ok(reflect_i_element(abs_nu, i_buf[0], k_val))
334}
335
336#[inline]
337fn besselk_internal<T: BesselFloat>(
338    nu: T,
339    z: Complex<T>,
340    scaling: Scaling,
341) -> Result<Complex<T>, Error> {
342    // K_{-ν}(z) = K_ν(z) (DLMF 10.27.3)
343    let abs_nu = nu.abs();
344    let zero = T::zero();
345    let mut y = [Complex::new(zero, zero)];
346    besk::zbesk(z, abs_nu, scaling, &mut y)?;
347    Ok(y[0])
348}
349
350#[inline]
351fn hankel_internal<T: BesselFloat>(
352    kind: HankelKind,
353    nu: T,
354    z: Complex<T>,
355    scaling: Scaling,
356) -> Result<Complex<T>, Error> {
357    let zero = T::zero();
358    if nu >= zero {
359        let mut y = [Complex::new(zero, zero)];
360        besh::zbesh(z, nu, kind, scaling, &mut y)?;
361        return Ok(y[0]);
362    }
363
364    let abs_nu = nu.abs();
365    let mut y = [Complex::new(zero, zero)];
366    besh::zbesh(z, abs_nu, kind, scaling, &mut y)?;
367
368    Ok(reflect_h_element(abs_nu, kind, y[0]))
369}
370
371// ── Single-value convenience functions ──
372
373/// Bessel function of the first kind, J<sub>ν</sub>(z).
374///
375/// Computes a single value of J_ν(z) for complex z and real order ν
376/// (any real value, including negative).
377///
378/// For negative ν, the DLMF 10.2.3 reflection formula is applied:
379/// `J_{-ν}(z) = cos(νπ) J_ν(z) - sin(νπ) Y_ν(z)`.
380/// For negative integer n, `J_{−n}(z) = (−1)^n J_n(z)`.
381///
382/// # Example
383///
384/// ```
385/// use complex_bessel::besselj;
386/// use num_complex::Complex;
387///
388/// let z = Complex::new(1.0_f64, 0.0);
389/// let j0 = besselj(0.0, z).unwrap();
390/// assert!((j0.re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
391/// ```
392///
393/// # Errors
394///
395/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
396/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
397/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
398/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
399#[inline]
400pub fn besselj<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
401    besselj_internal(nu, z, Scaling::Unscaled)
402}
403
404/// Bessel function of the second kind, Y<sub>ν</sub>(z).
405///
406/// Computes a single value of Y_ν(z) for complex z and real order ν
407/// (any real value, including negative).
408///
409/// For negative ν, the DLMF 10.2.3 reflection formula is applied:
410/// `Y_{-ν}(z) = sin(νπ) J_ν(z) + cos(νπ) Y_ν(z)`.
411/// For negative integer n, `Y_{−n}(z) = (−1)^n Y_n(z)`.
412///
413/// # Example
414///
415/// ```
416/// use complex_bessel::bessely;
417/// use num_complex::Complex;
418///
419/// let z = Complex::new(1.0_f64, 0.0);
420/// let y0 = bessely(0.0, z).unwrap();
421/// assert!((y0.re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
422/// ```
423///
424/// # Errors
425///
426/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
427/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
428/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
429/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
430#[inline]
431pub fn bessely<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
432    bessely_internal(nu, z, Scaling::Unscaled)
433}
434
435/// Modified Bessel function of the first kind, I<sub>ν</sub>(z).
436///
437/// Computes a single value of I_ν(z) for complex z and real order ν
438/// (any real value, including negative).
439///
440/// For negative ν, the DLMF 10.27.2 reflection formula is applied:
441/// `I_{-ν}(z) = I_ν(z) + (2/π) sin(νπ) K_ν(z)`.
442/// For negative integer n, `I_{−n}(z) = I_n(z)`.
443///
444/// # Example
445///
446/// ```
447/// use complex_bessel::besseli;
448/// use num_complex::Complex;
449///
450/// let z = Complex::new(1.0_f64, 0.0);
451/// let i0 = besseli(0.0, z).unwrap();
452/// assert!((i0.re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
453/// ```
454///
455/// # Errors
456///
457/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
458/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
459/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
460/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
461#[inline]
462pub fn besseli<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
463    besseli_internal(nu, z, Scaling::Unscaled)
464}
465
466/// Modified Bessel function of the second kind, K<sub>ν</sub>(z).
467///
468/// Computes a single value of K_ν(z) for complex z and real order ν
469/// (any real value, including negative).
470///
471/// For negative ν, K_{-ν}(z) = K_ν(z) (DLMF 10.27.3).
472///
473/// # Example
474///
475/// ```
476/// use complex_bessel::besselk;
477/// use num_complex::Complex;
478///
479/// let z = Complex::new(1.0_f64, 0.0);
480/// let k0 = besselk(0.0, z).unwrap();
481/// assert!((k0.re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
482/// ```
483///
484/// # Errors
485///
486/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
487/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
488/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
489/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
490#[inline]
491pub fn besselk<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
492    besselk_internal(nu, z, Scaling::Unscaled)
493}
494
495/// Hankel function of the first kind, H<sub>ν</sub><sup>(1)</sup>(z).
496///
497/// Computes a single value of H_ν^(1)(z) for complex z and real order ν
498/// (any real value, including negative).
499///
500/// For negative ν, the DLMF 10.4.6 reflection formula is applied:
501/// `H^(1)_{-ν}(z) = exp(νπi) H^(1)_ν(z)`.
502/// For negative integer n, `H^(1)_{−n}(z) = (−1)^n H^(1)_n(z)`.
503///
504/// # Example
505///
506/// ```
507/// use complex_bessel::hankel1;
508/// use num_complex::Complex;
509///
510/// let z = Complex::new(1.0_f64, 0.0);
511/// let h = hankel1(0.0, z).unwrap();
512/// assert!((h.re - 0.7652).abs() < 1e-3); // Re = J_0(1) ≈ 0.7652
513/// assert!((h.im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
514/// ```
515///
516/// # Errors
517///
518/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
519/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
520/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
521/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
522#[inline]
523pub fn hankel1<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
524    hankel_internal(HankelKind::First, nu, z, Scaling::Unscaled)
525}
526
527/// Hankel function of the second kind, H<sub>ν</sub><sup>(2)</sup>(z).
528///
529/// Computes a single value of H_ν^(2)(z) for complex z and real order ν
530/// (any real value, including negative).
531///
532/// For negative ν, the DLMF 10.4.6 reflection formula is applied:
533/// `H^(2)_{-ν}(z) = exp(-νπi) H^(2)_ν(z)`.
534/// For negative integer n, `H^(2)_{−n}(z) = (−1)^n H^(2)_n(z)`.
535///
536/// # Example
537///
538/// ```
539/// use complex_bessel::hankel2;
540/// use num_complex::Complex;
541///
542/// let z = Complex::new(1.0_f64, 0.0);
543/// let h = hankel2(0.0, z).unwrap();
544/// assert!((h.re - 0.7652).abs() < 1e-3); // Re = J_0(1) ≈ 0.7652
545/// assert!((h.im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
546/// ```
547///
548/// # Errors
549///
550/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
551/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
552/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
553/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
554#[inline]
555pub fn hankel2<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
556    hankel_internal(HankelKind::Second, nu, z, Scaling::Unscaled)
557}
558
559/// Airy function Ai(z).
560///
561/// Computes the Airy function Ai(z) for complex z.
562/// Ai(z) is a solution to the differential equation w′′ − z·w = 0
563/// that decays super-exponentially for large positive real z.
564///
565/// # Example
566///
567/// ```
568/// use complex_bessel::airy;
569/// use num_complex::Complex;
570///
571/// let z = Complex::new(0.0_f64, 0.0);
572/// let ai = airy(z).unwrap();
573/// assert!((ai.re - 0.3550).abs() < 1e-3); // Ai(0) ≈ 0.3550
574/// ```
575///
576/// # Errors
577///
578/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
579/// - [`Error::Overflow`] if |z| is too large for a finite result.
580/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
581/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
582#[inline]
583pub fn airy<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
584    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Unscaled)?;
585    Ok(result)
586}
587
588/// Derivative of the Airy function, Ai'(z).
589///
590/// Computes the derivative of Ai(z) for complex z.
591/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
592/// Satisfies the differential equation Ai′′(z) = z · Ai(z).
593///
594/// # Example
595///
596/// ```
597/// use complex_bessel::airyprime;
598/// use num_complex::Complex;
599///
600/// let z = Complex::new(0.0_f64, 0.0);
601/// let ai_prime = airyprime(z).unwrap();
602/// assert!((ai_prime.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
603/// ```
604///
605/// # Errors
606///
607/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
608/// - [`Error::Overflow`] if |z| is too large for a finite result.
609/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
610/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
611#[inline]
612pub fn airyprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
613    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
614    Ok(result)
615}
616
617/// Airy function of the second kind, Bi(z).
618///
619/// Computes the Airy function Bi(z) for complex z.
620/// Bi(z) is the solution to w′′ − z·w = 0 that grows super-exponentially
621/// for large positive real z.
622///
623/// # Example
624///
625/// ```
626/// use complex_bessel::biry;
627/// use num_complex::Complex;
628///
629/// let z = Complex::new(0.0_f64, 0.0);
630/// let bi = biry(z).unwrap();
631/// assert!((bi.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
632/// ```
633///
634/// # Errors
635///
636/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
637/// - [`Error::Overflow`] if |z| is too large for a finite result.
638/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
639/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
640#[inline]
641pub fn biry<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
642    let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Unscaled)?;
643    Ok(result)
644}
645
646/// Derivative of the Airy function of the second kind, Bi'(z).
647///
648/// Computes the derivative of Bi(z) for complex z.
649/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
650/// Satisfies the differential equation Bi′′(z) = z · Bi(z).
651///
652/// # Example
653///
654/// ```
655/// use complex_bessel::biryprime;
656/// use num_complex::Complex;
657///
658/// let z = Complex::new(0.0_f64, 0.0);
659/// let bi_prime = biryprime(z).unwrap();
660/// assert!((bi_prime.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
661/// ```
662///
663/// # Errors
664///
665/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
666/// - [`Error::Overflow`] if |z| is too large for a finite result.
667/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
668/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
669#[inline]
670pub fn biryprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
671    let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
672    Ok(result)
673}
674
675// ── Scaled single-value functions ──
676
677/// Scaled Bessel function of the first kind, exp(−|Im(z)|) · J<sub>ν</sub>(z).
678///
679/// The exponential factor cancels the asymptotic growth of J for large imaginary
680/// arguments, keeping results in a representable floating-point range.
681///
682/// Supports negative ν via the same reflection formula as [`besselj`].
683///
684/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
685///
686/// # Example
687///
688/// ```
689/// use complex_bessel::besselj_scaled;
690/// use num_complex::Complex;
691///
692/// let z = Complex::new(0.0_f64, 10.0);
693/// let j_s = besselj_scaled(0.0, z).unwrap();
694/// assert!((j_s.re - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * J_0(10i) ≈ 0.1278
695/// ```
696///
697/// # Errors
698///
699/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
700/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
701/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
702/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
703#[inline]
704pub fn besselj_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
705    besselj_internal(nu, z, Scaling::Exponential)
706}
707
708/// Scaled Bessel function of the second kind, exp(−|Im(z)|) · Y<sub>ν</sub>(z).
709///
710/// The exponential factor cancels the asymptotic growth of Y for large imaginary
711/// arguments, keeping results in a representable floating-point range.
712///
713/// Supports negative ν via the same reflection formula as [`bessely`].
714///
715/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
716///
717/// # Example
718///
719/// ```
720/// use complex_bessel::bessely_scaled;
721/// use num_complex::Complex;
722///
723/// let z = Complex::new(0.0_f64, 10.0);
724/// let y_s = bessely_scaled(0.0, z).unwrap();
725/// assert!((y_s.im - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * Y_0(10i), Im ≈ 0.1278
726/// ```
727///
728/// # Errors
729///
730/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
731/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
732/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
733/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
734#[inline]
735pub fn bessely_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
736    bessely_internal(nu, z, Scaling::Exponential)
737}
738
739/// Scaled modified Bessel function of the first kind, exp(−|Re(z)|) · I<sub>ν</sub>(z).
740///
741/// I_ν(z) grows exponentially for large |Re(z)|, so the unscaled value can
742/// easily overflow. The scaling factor exp(−|Re(z)|) keeps the result finite.
743///
744/// Supports negative ν via the same reflection formula as [`besseli`].
745///
746/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
747///
748/// # Example
749///
750/// ```
751/// use complex_bessel::besseli_scaled;
752/// use num_complex::Complex;
753///
754/// let z = Complex::new(10.0_f64, 0.0);
755/// let i_s = besseli_scaled(0.0, z).unwrap();
756/// assert!((i_s.re - 0.1278).abs() < 1e-3); // exp(-10) * I_0(10) ≈ 0.1278
757/// ```
758///
759/// # Errors
760///
761/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
762/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
763/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
764/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
765#[inline]
766pub fn besseli_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
767    besseli_internal(nu, z, Scaling::Exponential)
768}
769
770/// Scaled modified Bessel function of the second kind, exp(z) · K<sub>ν</sub>(z).
771///
772/// K_ν(z) decays exponentially for large Re(z), so unscaled values can underflow
773/// to zero. The scaling factor exp(z) keeps the result in a normal range.
774///
775/// Supports negative ν via the same reflection formula as [`besselk`].
776///
777/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
778///
779/// # Example
780///
781/// ```
782/// use complex_bessel::besselk_scaled;
783/// use num_complex::Complex;
784///
785/// let z = Complex::new(500.0_f64, 0.0);
786///
787/// // K_0(500) would underflow to 0, but the scaled version stays finite:
788/// let k_s = besselk_scaled(0.0, z).unwrap();
789/// assert!((k_s.re - 0.0560).abs() < 1e-3); // exp(500) * K_0(500) ≈ 0.0560
790/// ```
791///
792/// # Errors
793///
794/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
795/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
796/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
797/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
798#[inline]
799pub fn besselk_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
800    besselk_internal(nu, z, Scaling::Exponential)
801}
802
803/// Scaled Hankel function of the first kind, exp(−iz) · H<sub>ν</sub><sup>(1)</sup>(z).
804///
805/// H^(1) grows exponentially in the lower half-plane;
806/// the scaling factor removes this growth, preventing overflow.
807///
808/// Supports negative ν via the same reflection formula as [`hankel1`].
809///
810/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
811///
812/// # Example
813///
814/// ```
815/// use complex_bessel::hankel1_scaled;
816/// use num_complex::Complex;
817///
818/// let z = Complex::new(1.0_f64, 0.0);
819/// let h_s = hankel1_scaled(0.0, z).unwrap();
820/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(-i) * H^(1)_0(1), Re ≈ 0.4877
821/// ```
822///
823/// # Errors
824///
825/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
826/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
827/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
828/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
829#[inline]
830pub fn hankel1_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
831    hankel_internal(HankelKind::First, nu, z, Scaling::Exponential)
832}
833
834/// Scaled Hankel function of the second kind, exp(iz) · H<sub>ν</sub><sup>(2)</sup>(z).
835///
836/// H^(2) grows exponentially in the upper half-plane;
837/// the scaling factor removes this growth, preventing overflow.
838///
839/// Supports negative ν via the same reflection formula as [`hankel2`].
840///
841/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
842///
843/// # Example
844///
845/// ```
846/// use complex_bessel::hankel2_scaled;
847/// use num_complex::Complex;
848///
849/// let z = Complex::new(1.0_f64, 0.0);
850/// let h_s = hankel2_scaled(0.0, z).unwrap();
851/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(i) * H^(2)_0(1), Re ≈ 0.4877
852/// ```
853///
854/// # Errors
855///
856/// - [`Error::InvalidInput`] if z or ν is not finite (NaN or infinite), or if z = 0.
857/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
858/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
859/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
860#[inline]
861pub fn hankel2_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
862    hankel_internal(HankelKind::Second, nu, z, Scaling::Exponential)
863}
864
865/// Scaled Airy function, exp(ζ) · Ai(z), where ζ = (2/3) z√z.
866///
867/// Ai(z) decays super-exponentially for large positive real z.
868/// The scaling factor exp(ζ) keeps the result representable.
869///
870/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
871///
872/// # Example
873///
874/// ```
875/// use complex_bessel::airy_scaled;
876/// use num_complex::Complex;
877///
878/// let z = Complex::new(5.0_f64, 0.0);
879/// let ai_s = airy_scaled(z).unwrap();
880/// assert!((ai_s.re - 0.1870).abs() < 1e-3); // exp(ζ) * Ai(5) ≈ 0.1870
881/// ```
882///
883/// # Errors
884///
885/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
886/// - [`Error::Overflow`] if |z| is too large for a finite result.
887/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
888/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
889#[inline]
890pub fn airy_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
891    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Exponential)?;
892    Ok(result)
893}
894
895/// Scaled derivative of the Airy function, exp(ζ) · Ai'(z), where ζ = (2/3) z√z.
896///
897/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
898/// The scaling factor exp(ζ) keeps the result representable.
899///
900/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
901///
902/// # Example
903///
904/// ```
905/// use complex_bessel::airyprime_scaled;
906/// use num_complex::Complex;
907///
908/// let z = Complex::new(5.0_f64, 0.0);
909/// let ai_s = airyprime_scaled(z).unwrap();
910/// assert!((ai_s.re + 0.4270).abs() < 1e-3); // exp(ζ) * Ai'(5) ≈ -0.4270
911/// ```
912///
913/// # Errors
914///
915/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
916/// - [`Error::Overflow`] if |z| is too large for a finite result.
917/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
918/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
919#[inline]
920pub fn airyprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
921    let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Exponential)?;
922    Ok(result)
923}
924
925/// Scaled Airy function of the second kind, exp(−|Re(ζ)|) · Bi(z), where ζ = (2/3) z√z.
926///
927/// Bi(z) grows super-exponentially for large positive real z.
928/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
929///
930/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
931///
932/// # Example
933///
934/// ```
935/// use complex_bessel::biry_scaled;
936/// use num_complex::Complex;
937///
938/// let z = Complex::new(5.0_f64, 0.0);
939/// let bi_s = biry_scaled(z).unwrap();
940/// assert!((bi_s.re - 0.3811).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi(5) ≈ 0.3811
941/// ```
942///
943/// # Errors
944///
945/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
946/// - [`Error::Overflow`] if |z| is too large for a finite result.
947/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
948/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
949#[inline]
950pub fn biry_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
951    let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Exponential)?;
952    Ok(result)
953}
954
955/// Scaled derivative of the Airy function of the second kind, exp(−|Re(ζ)|) · Bi'(z), where ζ = (2/3) z√z.
956///
957/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
958/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
959///
960/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
961///
962/// # Example
963///
964/// ```
965/// use complex_bessel::biryprime_scaled;
966/// use num_complex::Complex;
967///
968/// let z = Complex::new(5.0_f64, 0.0);
969/// let bi_s = biryprime_scaled(z).unwrap();
970/// assert!((bi_s.re - 0.8319).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi'(5) ≈ 0.8319
971/// ```
972///
973/// # Errors
974///
975/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
976/// - [`Error::Overflow`] if |z| is too large for a finite result.
977/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
978/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
979#[inline]
980pub fn biryprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
981    let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Exponential)?;
982    Ok(result)
983}
984
985// ── Airy _raw functions (expose Accuracy) ──
986
987/// Airy function Ai(z) with accuracy status.
988///
989/// Like [`airy`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
990/// - [`Accuracy::Normal`] — no significant precision loss
991/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
992///
993/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
994/// see [crate-level docs](crate#exponential-scaling) for details.
995///
996/// # Example
997///
998/// ```
999/// use complex_bessel::*;
1000/// use num_complex::Complex;
1001///
1002/// let z = Complex::new(0.0_f64, 0.0);
1003/// let result = airy_raw(z, Scaling::Unscaled).unwrap();
1004/// assert!((result.value.re - 0.3550).abs() < 1e-3); // Ai(0) ≈ 0.3550
1005/// assert!(matches!(result.status, Accuracy::Normal));
1006/// ```
1007///
1008/// # Errors
1009///
1010/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1011/// - [`Error::Overflow`] if |z| is too large for a finite result.
1012/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1013/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1014#[inline]
1015pub fn airy_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1016    let (value, _nz, status) = airy::zairy(z, AiryDerivative::Value, scaling)?;
1017    Ok(AiryResult { value, status })
1018}
1019
1020/// Derivative of the Airy function Ai'(z) with accuracy status.
1021///
1022/// Like [`airyprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1023/// - [`Accuracy::Normal`] — no significant precision loss
1024/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1025///
1026/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1027/// see [crate-level docs](crate#exponential-scaling) for details.
1028///
1029/// # Example
1030///
1031/// ```
1032/// use complex_bessel::*;
1033/// use num_complex::Complex;
1034///
1035/// let z = Complex::new(0.0_f64, 0.0);
1036/// let result = airyprime_raw(z, Scaling::Unscaled).unwrap();
1037/// assert!((result.value.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
1038/// assert!(matches!(result.status, Accuracy::Normal));
1039/// ```
1040///
1041/// # Errors
1042///
1043/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1044/// - [`Error::Overflow`] if |z| is too large for a finite result.
1045/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1046/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1047#[inline]
1048pub fn airyprime_raw<T: BesselFloat>(
1049    z: Complex<T>,
1050    scaling: Scaling,
1051) -> Result<AiryResult<T>, Error> {
1052    let (value, _nz, status) = airy::zairy(z, AiryDerivative::Derivative, scaling)?;
1053    Ok(AiryResult { value, status })
1054}
1055
1056/// Airy function of the second kind Bi(z) with accuracy status.
1057///
1058/// Like [`biry`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1059/// - [`Accuracy::Normal`] — no significant precision loss
1060/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1061///
1062/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1063/// see [crate-level docs](crate#exponential-scaling) for details.
1064///
1065/// # Example
1066///
1067/// ```
1068/// use complex_bessel::*;
1069/// use num_complex::Complex;
1070///
1071/// let z = Complex::new(0.0_f64, 0.0);
1072/// let result = biry_raw(z, Scaling::Unscaled).unwrap();
1073/// assert!((result.value.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
1074/// assert!(matches!(result.status, Accuracy::Normal));
1075/// ```
1076///
1077/// # Errors
1078///
1079/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1080/// - [`Error::Overflow`] if |z| is too large for a finite result.
1081/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1082/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1083#[inline]
1084pub fn biry_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1085    let (value, status) = airy::zbiry(z, AiryDerivative::Value, scaling)?;
1086    Ok(AiryResult { value, status })
1087}
1088
1089/// Derivative of the Airy function of the second kind Bi'(z) with accuracy status.
1090///
1091/// Like [`biryprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1092/// - [`Accuracy::Normal`] — no significant precision loss
1093/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1094///
1095/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1096/// see [crate-level docs](crate#exponential-scaling) for details.
1097///
1098/// # Example
1099///
1100/// ```
1101/// use complex_bessel::*;
1102/// use num_complex::Complex;
1103///
1104/// let z = Complex::new(0.0_f64, 0.0);
1105/// let result = biryprime_raw(z, Scaling::Unscaled).unwrap();
1106/// assert!((result.value.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
1107/// assert!(matches!(result.status, Accuracy::Normal));
1108/// ```
1109///
1110/// # Errors
1111///
1112/// - [`Error::InvalidInput`] if z is not finite (NaN or infinite).
1113/// - [`Error::Overflow`] if |z| is too large for a finite result.
1114/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1115/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1116#[inline]
1117pub fn biryprime_raw<T: BesselFloat>(
1118    z: Complex<T>,
1119    scaling: Scaling,
1120) -> Result<AiryResult<T>, Error> {
1121    let (value, status) = airy::zbiry(z, AiryDerivative::Derivative, scaling)?;
1122    Ok(AiryResult { value, status })
1123}
1124
1125// ── Sequence functions with scaling option (require alloc) ──
1126
1127#[cfg(feature = "alloc")]
1128extern crate alloc as alloc_crate;
1129
1130#[cfg(feature = "alloc")]
1131fn seq_helper<T: BesselFloat>(
1132    n: usize,
1133    f: impl FnOnce(&mut [Complex<T>]) -> Result<(usize, Accuracy), Error>,
1134) -> Result<BesselResult<T>, Error> {
1135    let zero = T::zero();
1136    let mut values = alloc_crate::vec![Complex::new(zero, zero); n];
1137    let (underflow_count, status) = f(&mut values)?;
1138    Ok(BesselResult {
1139        values,
1140        underflow_count,
1141        status,
1142    })
1143}
1144
1145/// Take the worse of two statuses (Reduced > Normal).
1146#[cfg(feature = "alloc")]
1147#[inline]
1148fn worse_status(a: Accuracy, b: Accuracy) -> Accuracy {
1149    match (a, b) {
1150        (Accuracy::Reduced, _) | (_, Accuracy::Reduced) => Accuracy::Reduced,
1151        _ => Accuracy::Normal,
1152    }
1153}
1154
1155/// Recount underflow: number of leading zero elements in the output.
1156#[cfg(feature = "alloc")]
1157#[inline]
1158fn recount_underflow<T: BesselFloat>(values: &[Complex<T>]) -> usize {
1159    let zero = Complex::new(T::zero(), T::zero());
1160    values.iter().take_while(|v| **v == zero).count()
1161}
1162
1163/// Compute the number of negative-order elements (before crossing zero).
1164/// For orders ν, ν+1, …, ν+n−1, this is the count where ν+j < 0.
1165#[cfg(feature = "alloc")]
1166#[inline]
1167fn neg_count<T: BesselFloat>(nu: T, n: usize) -> usize {
1168    if nu >= T::zero() {
1169        return 0;
1170    }
1171    // Number of j in [0,n) with nu + j < 0
1172    let abs_nu = nu.abs();
1173    // ceil(|nu|) gives the first j where nu+j >= 0
1174    let c = abs_nu.ceil();
1175    let cn = if let Some(v) = c.to_usize() { v } else { n };
1176    cn.min(n)
1177}
1178
1179// ── Negative-order _seq implementations ──
1180
1181/// K_seq with negative order support: K_{-ν} = K_ν (even function).
1182///
1183/// Orders: ν, ν+1, …, ν+n−1. Negative orders map to |order|.
1184/// - All negative: single call zbesk(|ν|-(n-1), n), reverse.
1185/// - All positive: single call zbesk(ν, n).
1186/// - Integer crossing: single call covering max(|ν|, ν+n-1), mirror.
1187/// - Non-integer crossing: two calls (neg lattice + pos lattice).
1188#[cfg(feature = "alloc")]
1189#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1190fn besselk_seq_neg<T: BesselFloat>(
1191    nu: T,
1192    z: Complex<T>,
1193    n: usize,
1194    scaling: Scaling,
1195) -> Result<BesselResult<T>, Error> {
1196    let zero = T::zero();
1197    let czero = Complex::new(zero, zero);
1198    let abs_nu = nu.abs();
1199    let nc = neg_count(nu, n);
1200
1201    // All negative: absolute values are |ν|, |ν|-1, …, |ν|-(nc-1) (descending)
1202    // = ascending from |ν|-(nc-1) to |ν|
1203    if nc == n {
1204        // All orders negative, e.g., ν=-3.7, n=3 → orders -3.7,-2.7,-1.7
1205        // |orders| = 3.7, 2.7, 1.7 → start at 1.7, compute 3 ascending → reverse
1206        let start = abs_nu - T::from_f64((n - 1) as f64);
1207        let mut buf = alloc_crate::vec![czero; n];
1208        let (_, status) = besk::zbesk(z, start, scaling, &mut buf)?;
1209        buf.reverse();
1210        let uc = recount_underflow(&buf);
1211        return Ok(BesselResult {
1212            values: buf,
1213            underflow_count: uc,
1214            status,
1215        });
1216    }
1217
1218    // Check if integer crossing (fractional part is 0 for integer nu)
1219    let is_int = as_integer(abs_nu).is_some();
1220
1221    if is_int {
1222        // Integer crossing: e.g., ν=-2, n=5 → orders -2,-1,0,1,2
1223        // All have same fractional part (0). One call covers all |orders|.
1224        // Max absolute order = max(|ν|, ν+n-1)
1225        let last_order = nu + T::from_f64((n - 1) as f64);
1226        let max_abs = if abs_nu > last_order {
1227            abs_nu
1228        } else {
1229            last_order
1230        };
1231        let buf_n = max_abs.to_usize().unwrap_or(0) + 1; // orders 0..=max_abs
1232        let mut buf = alloc_crate::vec![czero; buf_n];
1233        let (_, status) = besk::zbesk(z, zero, scaling, &mut buf)?;
1234
1235        let mut values = alloc_crate::vec![czero; n];
1236        for j in 0..n {
1237            let order = nu + T::from_f64(j as f64);
1238            let idx = order.abs().to_usize().unwrap_or(0);
1239            values[j] = buf[idx];
1240        }
1241        let uc = recount_underflow(&values);
1242        return Ok(BesselResult {
1243            values,
1244            underflow_count: uc,
1245            status,
1246        });
1247    }
1248
1249    // Non-integer crossing: two separate lattices
1250    // Negative part: orders ν, ν+1, …, ν+(nc-1) → |orders| descending
1251    // frac_neg = fractional part of |ν|
1252    let frac_neg = abs_nu - abs_nu.floor();
1253    let neg_start = frac_neg; // smallest |order| in negative lattice
1254    let mut neg_buf = alloc_crate::vec![czero; nc];
1255    let (_, status1) = besk::zbesk(z, neg_start, scaling, &mut neg_buf)?;
1256
1257    // Positive part: orders ν+nc, ν+nc+1, …, ν+n-1
1258    let pos_start = nu + T::from_f64(nc as f64);
1259    let pos_n = n - nc;
1260    let mut pos_buf = alloc_crate::vec![czero; pos_n];
1261    let (_, status2) = besk::zbesk(z, pos_start, scaling, &mut pos_buf)?;
1262
1263    let mut values = alloc_crate::vec![czero; n];
1264    // Negative part: buf[0]=K(frac_neg), buf[1]=K(frac_neg+1), …
1265    // Need to map: order ν+j → |ν+j| → index in neg_buf
1266    // |ν+j| = abs_nu - j, and neg_buf[k] = K(frac_neg + k)
1267    // So k = |ν+j| - frac_neg = (abs_nu - j) - frac_neg = floor(abs_nu) - j
1268    for j in 0..nc {
1269        let k = (abs_nu - T::from_f64(j as f64))
1270            .floor()
1271            .to_usize()
1272            .unwrap_or(0);
1273        values[j] = neg_buf[k];
1274    }
1275    // Positive part: direct copy
1276    for j in 0..pos_n {
1277        values[nc + j] = pos_buf[j];
1278    }
1279
1280    let status = worse_status(status1, status2);
1281    let uc = recount_underflow(&values);
1282    Ok(BesselResult {
1283        values,
1284        underflow_count: uc,
1285        status,
1286    })
1287}
1288
1289/// H_seq with negative order support: H^(m)_{-ν} = exp(±νπi)·H^(m)_ν.
1290///
1291/// Same lattice-splitting as K, but negative-order elements get rotation.
1292#[cfg(feature = "alloc")]
1293#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1294fn hankel_seq_neg<T: BesselFloat>(
1295    kind: HankelKind,
1296    nu: T,
1297    z: Complex<T>,
1298    n: usize,
1299    scaling: Scaling,
1300) -> Result<BesselResult<T>, Error> {
1301    let zero = T::zero();
1302    let czero = Complex::new(zero, zero);
1303    let abs_nu = nu.abs();
1304    let nc = neg_count(nu, n);
1305    let is_int = as_integer(abs_nu).is_some();
1306
1307    if nc == n {
1308        // All negative: compute at positive |orders| then rotate each
1309        let start = abs_nu - T::from_f64((n - 1) as f64);
1310        let mut buf = alloc_crate::vec![czero; n];
1311        let (_, status) = besh::zbesh(z, start, kind, scaling, &mut buf)?;
1312        buf.reverse();
1313        // Apply rotation: element j has |order| = abs_nu - j
1314        for j in 0..n {
1315            let order = abs_nu - T::from_f64(j as f64);
1316            buf[j] = reflect_h_element(order, kind, buf[j]);
1317        }
1318        let uc = recount_underflow(&buf);
1319        return Ok(BesselResult {
1320            values: buf,
1321            underflow_count: uc,
1322            status,
1323        });
1324    }
1325
1326    if is_int {
1327        // Integer crossing: single call, rotate negative elements
1328        let last_order = nu + T::from_f64((n - 1) as f64);
1329        let max_abs = if abs_nu > last_order {
1330            abs_nu
1331        } else {
1332            last_order
1333        };
1334        let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1335        let mut buf = alloc_crate::vec![czero; buf_n];
1336        let (_, status) = besh::zbesh(z, zero, kind, scaling, &mut buf)?;
1337
1338        let mut values = alloc_crate::vec![czero; n];
1339        for j in 0..n {
1340            let order = nu + T::from_f64(j as f64);
1341            let idx = order.abs().to_usize().unwrap_or(0);
1342            values[j] = buf[idx];
1343            if order < zero {
1344                values[j] = reflect_h_element(order.abs(), kind, values[j]);
1345            }
1346        }
1347        let uc = recount_underflow(&values);
1348        return Ok(BesselResult {
1349            values,
1350            underflow_count: uc,
1351            status,
1352        });
1353    }
1354
1355    // Non-integer crossing: two lattices
1356    let frac_neg = abs_nu - abs_nu.floor();
1357    let mut neg_buf = alloc_crate::vec![czero; nc];
1358    let (_, status1) = besh::zbesh(z, frac_neg, kind, scaling, &mut neg_buf)?;
1359
1360    let pos_start = nu + T::from_f64(nc as f64);
1361    let pos_n = n - nc;
1362    let mut pos_buf = alloc_crate::vec![czero; pos_n];
1363    let (_, status2) = besh::zbesh(z, pos_start, kind, scaling, &mut pos_buf)?;
1364
1365    let mut values = alloc_crate::vec![czero; n];
1366    for j in 0..nc {
1367        let abs_order = abs_nu - T::from_f64(j as f64);
1368        let k = abs_order.floor().to_usize().unwrap_or(0);
1369        values[j] = reflect_h_element(abs_order, kind, neg_buf[k]);
1370    }
1371    for j in 0..pos_n {
1372        values[nc + j] = pos_buf[j];
1373    }
1374
1375    let status = worse_status(status1, status2);
1376    let uc = recount_underflow(&values);
1377    Ok(BesselResult {
1378        values,
1379        underflow_count: uc,
1380        status,
1381    })
1382}
1383
1384/// J_seq with negative order support.
1385/// J_{-ν} = cos(νπ)·J_ν − sin(νπ)·Y_ν (non-integer)
1386/// J_{-n} = (-1)^n · J_n (integer)
1387#[cfg(feature = "alloc")]
1388#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1389fn besselj_seq_neg<T: BesselFloat>(
1390    nu: T,
1391    z: Complex<T>,
1392    n: usize,
1393    scaling: Scaling,
1394) -> Result<BesselResult<T>, Error> {
1395    let zero = T::zero();
1396    let czero = Complex::new(zero, zero);
1397    let abs_nu = nu.abs();
1398    let nc = neg_count(nu, n);
1399    let is_int = as_integer(abs_nu).is_some();
1400
1401    if is_int {
1402        // Integer: J_{-n}(z) = (-1)^n J_n(z). Same lattice.
1403        let last_order = nu + T::from_f64((n - 1) as f64);
1404        let max_abs = if abs_nu > last_order {
1405            abs_nu
1406        } else {
1407            last_order
1408        };
1409        let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1410        let mut buf = alloc_crate::vec![czero; buf_n];
1411        let (_, status) = besj::zbesj(z, zero, scaling, &mut buf)?;
1412
1413        let mut values = alloc_crate::vec![czero; n];
1414        for j in 0..n {
1415            let order = nu + T::from_f64(j as f64);
1416            let idx = order.abs().to_usize().unwrap_or(0);
1417            values[j] = buf[idx];
1418            if order < zero {
1419                let int_order = order.abs().to_i64().unwrap_or(0);
1420                values[j] = values[j] * integer_sign::<T>(int_order);
1421            }
1422        }
1423        let uc = recount_underflow(&values);
1424        return Ok(BesselResult {
1425            values,
1426            underflow_count: uc,
1427            status,
1428        });
1429    }
1430
1431    if nc == n {
1432        // All negative, non-integer: need J and Y at positive |orders|
1433        let start = abs_nu - T::from_f64((n - 1) as f64);
1434        let mut j_buf = alloc_crate::vec![czero; n];
1435        let mut y_buf = alloc_crate::vec![czero; n];
1436        let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
1437        let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
1438
1439        let mut values = alloc_crate::vec![czero; n];
1440        // buf[k] = f(start+k). For output j, |order| = abs_nu - j, k = |order| - start = n-1-j
1441        for j in 0..n {
1442            let k = n - 1 - j;
1443            let order = abs_nu - T::from_f64(j as f64);
1444            values[j] = reflect_j_element(order, j_buf[k], y_buf[k]);
1445        }
1446        let uc = recount_underflow(&values);
1447        return Ok(BesselResult {
1448            values,
1449            underflow_count: uc,
1450            status: worse_status(s1, s2),
1451        });
1452    }
1453
1454    // Non-integer crossing: two lattices
1455    let frac_neg = abs_nu - abs_nu.floor();
1456    let mut neg_j = alloc_crate::vec![czero; nc];
1457    let mut neg_y = alloc_crate::vec![czero; nc];
1458    let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
1459    let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
1460
1461    let pos_start = nu + T::from_f64(nc as f64);
1462    let pos_n = n - nc;
1463    let mut pos_buf = alloc_crate::vec![czero; pos_n];
1464    let (_, s3) = besj::zbesj(z, pos_start, scaling, &mut pos_buf)?;
1465
1466    let mut values = alloc_crate::vec![czero; n];
1467    for j in 0..nc {
1468        let abs_order = abs_nu - T::from_f64(j as f64);
1469        let k = abs_order.floor().to_usize().unwrap_or(0);
1470        values[j] = reflect_j_element(abs_order, neg_j[k], neg_y[k]);
1471    }
1472    for j in 0..pos_n {
1473        values[nc + j] = pos_buf[j];
1474    }
1475
1476    let status = worse_status(worse_status(s1, s2), s3);
1477    let uc = recount_underflow(&values);
1478    Ok(BesselResult {
1479        values,
1480        underflow_count: uc,
1481        status,
1482    })
1483}
1484
1485/// Y_seq with negative order support.
1486/// Y_{-ν} = sin(νπ)·J_ν + cos(νπ)·Y_ν (non-integer)
1487/// Y_{-n} = (-1)^n · Y_n (integer)
1488#[cfg(feature = "alloc")]
1489#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1490fn bessely_seq_neg<T: BesselFloat>(
1491    nu: T,
1492    z: Complex<T>,
1493    n: usize,
1494    scaling: Scaling,
1495) -> Result<BesselResult<T>, Error> {
1496    let zero = T::zero();
1497    let czero = Complex::new(zero, zero);
1498    let abs_nu = nu.abs();
1499    let nc = neg_count(nu, n);
1500    let is_int = as_integer(abs_nu).is_some();
1501
1502    if is_int {
1503        let last_order = nu + T::from_f64((n - 1) as f64);
1504        let max_abs = if abs_nu > last_order {
1505            abs_nu
1506        } else {
1507            last_order
1508        };
1509        let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1510        let mut buf = alloc_crate::vec![czero; buf_n];
1511        let (_, status) = besy::zbesy(z, zero, scaling, &mut buf)?;
1512
1513        let mut values = alloc_crate::vec![czero; n];
1514        for j in 0..n {
1515            let order = nu + T::from_f64(j as f64);
1516            let idx = order.abs().to_usize().unwrap_or(0);
1517            values[j] = buf[idx];
1518            if order < zero {
1519                let int_order = order.abs().to_i64().unwrap_or(0);
1520                values[j] = values[j] * integer_sign::<T>(int_order);
1521            }
1522        }
1523        let uc = recount_underflow(&values);
1524        return Ok(BesselResult {
1525            values,
1526            underflow_count: uc,
1527            status,
1528        });
1529    }
1530
1531    if nc == n {
1532        let start = abs_nu - T::from_f64((n - 1) as f64);
1533        let mut j_buf = alloc_crate::vec![czero; n];
1534        let mut y_buf = alloc_crate::vec![czero; n];
1535        let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
1536        let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
1537
1538        let mut values = alloc_crate::vec![czero; n];
1539        for j in 0..n {
1540            let k = n - 1 - j;
1541            let order = abs_nu - T::from_f64(j as f64);
1542            values[j] = reflect_y_element(order, j_buf[k], y_buf[k]);
1543        }
1544        let uc = recount_underflow(&values);
1545        return Ok(BesselResult {
1546            values,
1547            underflow_count: uc,
1548            status: worse_status(s1, s2),
1549        });
1550    }
1551
1552    // Non-integer crossing: two lattices
1553    let frac_neg = abs_nu - abs_nu.floor();
1554    let mut neg_j = alloc_crate::vec![czero; nc];
1555    let mut neg_y = alloc_crate::vec![czero; nc];
1556    let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
1557    let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
1558
1559    let pos_start = nu + T::from_f64(nc as f64);
1560    let pos_n = n - nc;
1561    let mut pos_buf = alloc_crate::vec![czero; pos_n];
1562    let (_, s3) = besy::zbesy(z, pos_start, scaling, &mut pos_buf)?;
1563
1564    let mut values = alloc_crate::vec![czero; n];
1565    for j in 0..nc {
1566        let abs_order = abs_nu - T::from_f64(j as f64);
1567        let k = abs_order.floor().to_usize().unwrap_or(0);
1568        values[j] = reflect_y_element(abs_order, neg_j[k], neg_y[k]);
1569    }
1570    for j in 0..pos_n {
1571        values[nc + j] = pos_buf[j];
1572    }
1573
1574    let status = worse_status(worse_status(s1, s2), s3);
1575    let uc = recount_underflow(&values);
1576    Ok(BesselResult {
1577        values,
1578        underflow_count: uc,
1579        status,
1580    })
1581}
1582
1583/// I_seq with negative order support.
1584/// I_{-ν} = I_ν + (2/π)sin(νπ)K_ν (non-integer)
1585/// I_{-n} = I_n (integer)
1586#[cfg(feature = "alloc")]
1587#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1588fn besseli_seq_neg<T: BesselFloat>(
1589    nu: T,
1590    z: Complex<T>,
1591    n: usize,
1592    scaling: Scaling,
1593) -> Result<BesselResult<T>, Error> {
1594    let zero = T::zero();
1595    let czero = Complex::new(zero, zero);
1596    let abs_nu = nu.abs();
1597    let nc = neg_count(nu, n);
1598    let is_int = as_integer(abs_nu).is_some();
1599
1600    if is_int {
1601        // I_{-n} = I_n. Same lattice, no correction needed.
1602        let last_order = nu + T::from_f64((n - 1) as f64);
1603        let max_abs = if abs_nu > last_order {
1604            abs_nu
1605        } else {
1606            last_order
1607        };
1608        let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1609        let mut buf = alloc_crate::vec![czero; buf_n];
1610        let (_, status) = besi::zbesi(z, zero, scaling, &mut buf)?;
1611
1612        let mut values = alloc_crate::vec![czero; n];
1613        for j in 0..n {
1614            let order = nu + T::from_f64(j as f64);
1615            let idx = order.abs().to_usize().unwrap_or(0);
1616            values[j] = buf[idx];
1617        }
1618        let uc = recount_underflow(&values);
1619        return Ok(BesselResult {
1620            values,
1621            underflow_count: uc,
1622            status,
1623        });
1624    }
1625
1626    if nc == n {
1627        // All negative, non-integer: need I and K at positive |orders|
1628        let start = abs_nu - T::from_f64((n - 1) as f64);
1629        let mut i_buf = alloc_crate::vec![czero; n];
1630        let mut k_buf = alloc_crate::vec![czero; n];
1631        let (_, s1) = besi::zbesi(z, start, scaling, &mut i_buf)?;
1632        let (_, s2) = besk::zbesk(z, start, scaling, &mut k_buf)?;
1633
1634        let mut values = alloc_crate::vec![czero; n];
1635        for j in 0..n {
1636            let k_idx = n - 1 - j;
1637            let order = abs_nu - T::from_f64(j as f64);
1638            let mut k_val = k_buf[k_idx];
1639            if scaling == Scaling::Exponential {
1640                k_val = k_to_i_scaling_correction(z, k_val);
1641            }
1642            values[j] = reflect_i_element(order, i_buf[k_idx], k_val);
1643        }
1644        let uc = recount_underflow(&values);
1645        return Ok(BesselResult {
1646            values,
1647            underflow_count: uc,
1648            status: worse_status(s1, s2),
1649        });
1650    }
1651
1652    // Non-integer crossing: two lattices
1653    let frac_neg = abs_nu - abs_nu.floor();
1654    let mut neg_i = alloc_crate::vec![czero; nc];
1655    let mut neg_k = alloc_crate::vec![czero; nc];
1656    let (_, s1) = besi::zbesi(z, frac_neg, scaling, &mut neg_i)?;
1657    let (_, s2) = besk::zbesk(z, frac_neg, scaling, &mut neg_k)?;
1658
1659    let pos_start = nu + T::from_f64(nc as f64);
1660    let pos_n = n - nc;
1661    let mut pos_buf = alloc_crate::vec![czero; pos_n];
1662    let (_, s3) = besi::zbesi(z, pos_start, scaling, &mut pos_buf)?;
1663
1664    let mut values = alloc_crate::vec![czero; n];
1665    for j in 0..nc {
1666        let abs_order = abs_nu - T::from_f64(j as f64);
1667        let k_idx = abs_order.floor().to_usize().unwrap_or(0);
1668        let mut k_val = neg_k[k_idx];
1669        if scaling == Scaling::Exponential {
1670            k_val = k_to_i_scaling_correction(z, k_val);
1671        }
1672        values[j] = reflect_i_element(abs_order, neg_i[k_idx], k_val);
1673    }
1674    for j in 0..pos_n {
1675        values[nc + j] = pos_buf[j];
1676    }
1677
1678    let status = worse_status(worse_status(s1, s2), s3);
1679    let uc = recount_underflow(&values);
1680    Ok(BesselResult {
1681        values,
1682        underflow_count: uc,
1683        status,
1684    })
1685}
1686
1687#[cfg(feature = "alloc")]
1688/// Compute J<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1689///
1690/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1691/// - [`Accuracy::Normal`] — no significant precision loss
1692/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1693///
1694/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1695/// see [crate-level docs](crate#exponential-scaling) for details.
1696///
1697/// Supports negative ν via the same reflection formula as [`besselj`].
1698///
1699/// # Example
1700///
1701/// ```
1702/// # #[cfg(feature = "alloc")] {
1703/// use complex_bessel::*;
1704/// use num_complex::Complex;
1705///
1706/// let z = Complex::new(1.0_f64, 0.0);
1707///
1708/// // J_0(z), J_1(z), J_2(z) in one call
1709/// let result = besselj_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1710/// assert_eq!(result.values.len(), 3);
1711/// assert!((result.values[0].re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
1712/// # }
1713/// ```
1714///
1715/// # Errors
1716///
1717/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
1718/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1719/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1720/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1721pub fn besselj_seq<T: BesselFloat>(
1722    nu: T,
1723    z: Complex<T>,
1724    n: usize,
1725    scaling: Scaling,
1726) -> Result<BesselResult<T>, Error> {
1727    if nu < T::zero() {
1728        return besselj_seq_neg(nu, z, n, scaling);
1729    }
1730    seq_helper(n, |y| besj::zbesj(z, nu, scaling, y))
1731}
1732
1733#[cfg(feature = "alloc")]
1734/// Compute Y<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1735///
1736/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1737/// - [`Accuracy::Normal`] — no significant precision loss
1738/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1739///
1740/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1741/// see [crate-level docs](crate#exponential-scaling) for details.
1742///
1743/// Supports negative ν via the same reflection formula as [`bessely`].
1744///
1745/// # Example
1746///
1747/// ```
1748/// # #[cfg(feature = "alloc")] {
1749/// use complex_bessel::*;
1750/// use num_complex::Complex;
1751///
1752/// let z = Complex::new(1.0_f64, 0.0);
1753///
1754/// // Y_0(z), Y_1(z), Y_2(z) in one call
1755/// let result = bessely_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1756/// assert_eq!(result.values.len(), 3);
1757/// assert!((result.values[0].re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
1758/// # }
1759/// ```
1760///
1761/// # Errors
1762///
1763/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
1764/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1765/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1766/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1767pub fn bessely_seq<T: BesselFloat>(
1768    nu: T,
1769    z: Complex<T>,
1770    n: usize,
1771    scaling: Scaling,
1772) -> Result<BesselResult<T>, Error> {
1773    if nu < T::zero() {
1774        return bessely_seq_neg(nu, z, n, scaling);
1775    }
1776    seq_helper(n, |y| besy::zbesy(z, nu, scaling, y))
1777}
1778
1779#[cfg(feature = "alloc")]
1780/// Compute I<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1781///
1782/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1783/// - [`Accuracy::Normal`] — no significant precision loss
1784/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1785///
1786/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1787/// see [crate-level docs](crate#exponential-scaling) for details.
1788///
1789/// Supports negative ν via the same reflection formula as [`besseli`].
1790///
1791/// # Example
1792///
1793/// ```
1794/// # #[cfg(feature = "alloc")] {
1795/// use complex_bessel::*;
1796/// use num_complex::Complex;
1797///
1798/// let z = Complex::new(1.0_f64, 0.0);
1799///
1800/// // I_0(z), I_1(z), I_2(z) in one call
1801/// let result = besseli_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1802/// assert_eq!(result.values.len(), 3);
1803/// assert!((result.values[0].re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
1804/// # }
1805/// ```
1806///
1807/// # Errors
1808///
1809/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0 and ν is a negative non-integer.
1810/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1811/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1812/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1813pub fn besseli_seq<T: BesselFloat>(
1814    nu: T,
1815    z: Complex<T>,
1816    n: usize,
1817    scaling: Scaling,
1818) -> Result<BesselResult<T>, Error> {
1819    if nu < T::zero() {
1820        return besseli_seq_neg(nu, z, n, scaling);
1821    }
1822    seq_helper(n, |y| besi::zbesi(z, nu, scaling, y))
1823}
1824
1825#[cfg(feature = "alloc")]
1826/// Compute K<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1827///
1828/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1829/// - [`Accuracy::Normal`] — no significant precision loss
1830/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1831///
1832/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1833/// see [crate-level docs](crate#exponential-scaling) for details.
1834///
1835/// Supports negative ν via the same reflection formula as [`besselk`].
1836///
1837/// # Example
1838///
1839/// ```
1840/// # #[cfg(feature = "alloc")] {
1841/// use complex_bessel::*;
1842/// use num_complex::Complex;
1843///
1844/// let z = Complex::new(1.0_f64, 0.0);
1845///
1846/// // K_0(z), K_1(z), K_2(z) in one call
1847/// let result = besselk_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1848/// assert_eq!(result.values.len(), 3);
1849/// assert!((result.values[0].re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
1850/// # }
1851/// ```
1852///
1853/// # Errors
1854///
1855/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
1856/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1857/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1858/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1859pub fn besselk_seq<T: BesselFloat>(
1860    nu: T,
1861    z: Complex<T>,
1862    n: usize,
1863    scaling: Scaling,
1864) -> Result<BesselResult<T>, Error> {
1865    if nu < T::zero() {
1866        return besselk_seq_neg(nu, z, n, scaling);
1867    }
1868    seq_helper(n, |y| besk::zbesk(z, nu, scaling, y))
1869}
1870
1871#[cfg(feature = "alloc")]
1872/// Compute H<sub>ν+j</sub><sup>(1)</sup>(z) for j = 0, 1, …, n−1 in a single call.
1873///
1874/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1875/// - [`Accuracy::Normal`] — no significant precision loss
1876/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1877///
1878/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1879/// see [crate-level docs](crate#exponential-scaling) for details.
1880///
1881/// Supports negative ν via the same reflection formula as [`hankel1`].
1882///
1883/// # Example
1884///
1885/// ```
1886/// # #[cfg(feature = "alloc")] {
1887/// use complex_bessel::*;
1888/// use num_complex::Complex;
1889///
1890/// let z = Complex::new(1.0_f64, 0.0);
1891///
1892/// // H^(1)_0(z), H^(1)_1(z) in one call
1893/// let result = hankel1_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
1894/// assert_eq!(result.values.len(), 2);
1895/// assert!((result.values[0].im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
1896/// # }
1897/// ```
1898///
1899/// # Errors
1900///
1901/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
1902/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1903/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1904/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1905pub fn hankel1_seq<T: BesselFloat>(
1906    nu: T,
1907    z: Complex<T>,
1908    n: usize,
1909    scaling: Scaling,
1910) -> Result<BesselResult<T>, Error> {
1911    if nu < T::zero() {
1912        return hankel_seq_neg(HankelKind::First, nu, z, n, scaling);
1913    }
1914    seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::First, scaling, y))
1915}
1916
1917#[cfg(feature = "alloc")]
1918/// Compute H<sub>ν+j</sub><sup>(2)</sup>(z) for j = 0, 1, …, n−1 in a single call.
1919///
1920/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1921/// - [`Accuracy::Normal`] — no significant precision loss
1922/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1923///
1924/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1925/// see [crate-level docs](crate#exponential-scaling) for details.
1926///
1927/// Supports negative ν via the same reflection formula as [`hankel2`].
1928///
1929/// # Example
1930///
1931/// ```
1932/// # #[cfg(feature = "alloc")] {
1933/// use complex_bessel::*;
1934/// use num_complex::Complex;
1935///
1936/// let z = Complex::new(1.0_f64, 0.0);
1937///
1938/// // H^(2)_0(z), H^(2)_1(z) in one call
1939/// let result = hankel2_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
1940/// assert_eq!(result.values.len(), 2);
1941/// assert!((result.values[0].im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
1942/// # }
1943/// ```
1944///
1945/// # Errors
1946///
1947/// - [`Error::InvalidInput`] if n < 1, if z or ν is not finite (NaN or infinite), or if z = 0.
1948/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1949/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1950/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1951pub fn hankel2_seq<T: BesselFloat>(
1952    nu: T,
1953    z: Complex<T>,
1954    n: usize,
1955    scaling: Scaling,
1956) -> Result<BesselResult<T>, Error> {
1957    if nu < T::zero() {
1958        return hankel_seq_neg(HankelKind::Second, nu, z, n, scaling);
1959    }
1960    seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::Second, scaling, y))
1961}
1962
1963// ── Tests for non-finite input rejection at the public API level ──
1964// Deliberately gated on test only (not alloc), so they run in every
1965// feature configuration.
1966
1967#[cfg(test)]
1968mod non_finite_tests {
1969    use super::*;
1970    use num_complex::{Complex32, Complex64};
1971
1972    #[test]
1973    fn bessel_non_finite_z_returns_invalid_input() {
1974        let nan = f64::NAN;
1975        for z in [
1976            Complex64::new(nan, 0.0),
1977            Complex64::new(0.0, nan),
1978            Complex64::new(nan, nan),
1979            Complex64::new(f64::INFINITY, 0.0),
1980        ] {
1981            assert!(matches!(besselj(0.0, z), Err(Error::InvalidInput)));
1982            assert!(matches!(besselj_scaled(1.0, z), Err(Error::InvalidInput)));
1983            assert!(matches!(bessely(0.0, z), Err(Error::InvalidInput)));
1984            assert!(matches!(besseli(0.0, z), Err(Error::InvalidInput)));
1985            assert!(matches!(besselk(0.0, z), Err(Error::InvalidInput)));
1986            assert!(matches!(hankel1(0.0, z), Err(Error::InvalidInput)));
1987            assert!(matches!(hankel2(0.0, z), Err(Error::InvalidInput)));
1988            // Negative order exercises the reflection path in the wrappers.
1989            assert!(matches!(besselj(-2.5, z), Err(Error::InvalidInput)));
1990            assert!(matches!(besselk(-2.5, z), Err(Error::InvalidInput)));
1991        }
1992    }
1993
1994    #[test]
1995    fn bessel_non_finite_nu_returns_invalid_input() {
1996        let z = Complex64::new(1.0, 1.0);
1997        for nu in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1998            assert!(matches!(besselj(nu, z), Err(Error::InvalidInput)));
1999            assert!(matches!(bessely(nu, z), Err(Error::InvalidInput)));
2000            assert!(matches!(besseli(nu, z), Err(Error::InvalidInput)));
2001            assert!(matches!(besselk(nu, z), Err(Error::InvalidInput)));
2002            assert!(matches!(hankel1(nu, z), Err(Error::InvalidInput)));
2003            assert!(matches!(hankel2(nu, z), Err(Error::InvalidInput)));
2004        }
2005    }
2006
2007    #[test]
2008    fn bessel_non_finite_f32_returns_invalid_input() {
2009        let z = Complex32::new(1.0, 1.0);
2010        assert!(matches!(
2011            besselj(0.0f32, Complex32::new(f32::NAN, 0.0)),
2012            Err(Error::InvalidInput)
2013        ));
2014        assert!(matches!(besselj(f32::NAN, z), Err(Error::InvalidInput)));
2015    }
2016
2017    #[test]
2018    fn airy_non_finite_z_returns_invalid_input() {
2019        let nan = f64::NAN;
2020        for z in [
2021            Complex64::new(nan, 0.0),
2022            Complex64::new(0.0, nan),
2023            Complex64::new(f64::INFINITY, 0.0),
2024        ] {
2025            assert!(matches!(airy(z), Err(Error::InvalidInput)));
2026            assert!(matches!(airyprime(z), Err(Error::InvalidInput)));
2027            assert!(matches!(biry(z), Err(Error::InvalidInput)));
2028            assert!(matches!(biryprime(z), Err(Error::InvalidInput)));
2029            assert!(matches!(
2030                airy_raw(z, Scaling::Unscaled),
2031                Err(Error::InvalidInput)
2032            ));
2033            assert!(matches!(
2034                biry_raw(z, Scaling::Unscaled),
2035                Err(Error::InvalidInput)
2036            ));
2037        }
2038    }
2039
2040    #[test]
2041    fn finite_overflowing_modulus_keeps_total_precision_loss() {
2042        // |z| overflows to infinity while both components are finite: this
2043        // must stay TotalPrecisionLoss (range check), not InvalidInput.
2044        let z = Complex64::new(1.7e308, 1.7e308);
2045        assert!(matches!(besselj(0.0, z), Err(Error::TotalPrecisionLoss)));
2046    }
2047
2048    #[cfg(feature = "alloc")]
2049    #[test]
2050    fn seq_non_finite_inputs_return_invalid_input() {
2051        let z_nan = Complex64::new(f64::NAN, 0.0);
2052        let z = Complex64::new(1.0, 1.0);
2053        assert!(matches!(
2054            besselj_seq(f64::NAN, z, 2, Scaling::Unscaled),
2055            Err(Error::InvalidInput)
2056        ));
2057        assert!(matches!(
2058            besselj_seq(0.0, z_nan, 2, Scaling::Unscaled),
2059            Err(Error::InvalidInput)
2060        ));
2061        assert!(matches!(
2062            besselk_seq(-2.5, z_nan, 3, Scaling::Unscaled),
2063            Err(Error::InvalidInput)
2064        ));
2065    }
2066}
2067
2068// ── Tests for negative order _seq functions ──
2069
2070#[cfg(all(test, feature = "alloc"))]
2071mod neg_order_seq_tests {
2072    use super::*;
2073    use num_complex::Complex64;
2074
2075    const TOL: f64 = 2e-14;
2076
2077    fn rel_err(a: Complex64, b: Complex64) -> f64 {
2078        let diff = (a - b).norm();
2079        let mag = a.norm().max(b.norm());
2080        if mag == 0.0 { diff } else { diff / mag }
2081    }
2082
2083    /// Verify _seq(ν, z, n)[j] == single_value(ν+j, z) for all j.
2084    #[allow(clippy::too_many_arguments)]
2085    fn check_seq_vs_single<F, G>(
2086        seq_fn: F,
2087        single_fn: G,
2088        nu: f64,
2089        z: Complex64,
2090        n: usize,
2091        scaling: Scaling,
2092        tol: f64,
2093        label: &str,
2094    ) where
2095        F: FnOnce(f64, Complex64, usize, Scaling) -> Result<BesselResult<f64>, Error>,
2096        G: Fn(f64, Complex64) -> Result<Complex64, Error>,
2097    {
2098        let result = seq_fn(nu, z, n, scaling).unwrap();
2099        assert_eq!(result.values.len(), n, "{label}: wrong length");
2100        for j in 0..n {
2101            let order = nu + j as f64;
2102            let expected = single_fn(order, z).unwrap();
2103            let err = rel_err(result.values[j], expected);
2104            assert!(
2105                err < tol,
2106                "{label}: order={order}, seq={:?}, single={expected:?}, rel_err={err}",
2107                result.values[j]
2108            );
2109        }
2110    }
2111
2112    // ── K (even function) ──
2113
2114    #[test]
2115    fn besselk_seq_all_negative_nonint() {
2116        let z = Complex64::new(1.5, 0.5);
2117        check_seq_vs_single(
2118            besselk_seq,
2119            besselk,
2120            -3.7,
2121            z,
2122            3,
2123            Scaling::Unscaled,
2124            TOL,
2125            "K all neg",
2126        );
2127    }
2128
2129    #[test]
2130    fn besselk_seq_crossing_nonint() {
2131        let z = Complex64::new(1.5, 0.5);
2132        check_seq_vs_single(
2133            besselk_seq,
2134            besselk,
2135            -1.5,
2136            z,
2137            5,
2138            Scaling::Unscaled,
2139            TOL,
2140            "K crossing nonint",
2141        );
2142    }
2143
2144    #[test]
2145    fn besselk_seq_int_negative() {
2146        let z = Complex64::new(1.5, 0.5);
2147        check_seq_vs_single(
2148            besselk_seq,
2149            besselk,
2150            -3.0,
2151            z,
2152            4,
2153            Scaling::Unscaled,
2154            TOL,
2155            "K int neg",
2156        );
2157    }
2158
2159    #[test]
2160    fn besselk_seq_int_crossing() {
2161        let z = Complex64::new(1.5, 0.5);
2162        check_seq_vs_single(
2163            besselk_seq,
2164            besselk,
2165            -2.0,
2166            z,
2167            5,
2168            Scaling::Unscaled,
2169            TOL,
2170            "K int cross",
2171        );
2172    }
2173
2174    #[test]
2175    fn besselk_seq_neg_zero() {
2176        let z = Complex64::new(1.0, 0.0);
2177        check_seq_vs_single(
2178            besselk_seq,
2179            besselk,
2180            -0.0,
2181            z,
2182            1,
2183            Scaling::Unscaled,
2184            TOL,
2185            "K neg zero",
2186        );
2187    }
2188
2189    // ── H^(1) ──
2190
2191    #[test]
2192    fn hankel1_seq_all_negative_nonint() {
2193        let z = Complex64::new(1.5, 0.5);
2194        check_seq_vs_single(
2195            hankel1_seq,
2196            hankel1,
2197            -3.7,
2198            z,
2199            3,
2200            Scaling::Unscaled,
2201            TOL,
2202            "H1 all neg",
2203        );
2204    }
2205
2206    #[test]
2207    fn hankel1_seq_crossing_nonint() {
2208        let z = Complex64::new(1.5, 0.5);
2209        check_seq_vs_single(
2210            hankel1_seq,
2211            hankel1,
2212            -1.5,
2213            z,
2214            5,
2215            Scaling::Unscaled,
2216            TOL,
2217            "H1 crossing",
2218        );
2219    }
2220
2221    #[test]
2222    fn hankel1_seq_int_crossing() {
2223        let z = Complex64::new(1.5, 0.5);
2224        check_seq_vs_single(
2225            hankel1_seq,
2226            hankel1,
2227            -2.0,
2228            z,
2229            5,
2230            Scaling::Unscaled,
2231            TOL,
2232            "H1 int cross",
2233        );
2234    }
2235
2236    // ── H^(2) ──
2237
2238    #[test]
2239    fn hankel2_seq_all_negative_nonint() {
2240        let z = Complex64::new(1.5, 0.5);
2241        check_seq_vs_single(
2242            hankel2_seq,
2243            hankel2,
2244            -3.7,
2245            z,
2246            3,
2247            Scaling::Unscaled,
2248            TOL,
2249            "H2 all neg",
2250        );
2251    }
2252
2253    #[test]
2254    fn hankel2_seq_crossing_nonint() {
2255        let z = Complex64::new(1.5, 0.5);
2256        check_seq_vs_single(
2257            hankel2_seq,
2258            hankel2,
2259            -1.5,
2260            z,
2261            5,
2262            Scaling::Unscaled,
2263            TOL,
2264            "H2 crossing",
2265        );
2266    }
2267
2268    #[test]
2269    fn hankel2_seq_int_crossing() {
2270        let z = Complex64::new(1.5, 0.5);
2271        check_seq_vs_single(
2272            hankel2_seq,
2273            hankel2,
2274            -2.0,
2275            z,
2276            5,
2277            Scaling::Unscaled,
2278            TOL,
2279            "H2 int cross",
2280        );
2281    }
2282
2283    // ── J ──
2284
2285    #[test]
2286    fn besselj_seq_all_negative_nonint() {
2287        let z = Complex64::new(1.5, 0.5);
2288        check_seq_vs_single(
2289            besselj_seq,
2290            besselj,
2291            -3.7,
2292            z,
2293            3,
2294            Scaling::Unscaled,
2295            TOL,
2296            "J all neg",
2297        );
2298    }
2299
2300    #[test]
2301    fn besselj_seq_crossing_nonint() {
2302        let z = Complex64::new(1.5, 0.5);
2303        check_seq_vs_single(
2304            besselj_seq,
2305            besselj,
2306            -1.5,
2307            z,
2308            5,
2309            Scaling::Unscaled,
2310            TOL,
2311            "J crossing",
2312        );
2313    }
2314
2315    #[test]
2316    fn besselj_seq_int_negative() {
2317        let z = Complex64::new(1.5, 0.5);
2318        check_seq_vs_single(
2319            besselj_seq,
2320            besselj,
2321            -3.0,
2322            z,
2323            4,
2324            Scaling::Unscaled,
2325            TOL,
2326            "J int neg",
2327        );
2328    }
2329
2330    #[test]
2331    fn besselj_seq_int_crossing() {
2332        let z = Complex64::new(1.5, 0.5);
2333        check_seq_vs_single(
2334            besselj_seq,
2335            besselj,
2336            -2.0,
2337            z,
2338            5,
2339            Scaling::Unscaled,
2340            TOL,
2341            "J int cross",
2342        );
2343    }
2344
2345    // ── Y ──
2346
2347    #[test]
2348    fn bessely_seq_all_negative_nonint() {
2349        let z = Complex64::new(1.5, 0.5);
2350        check_seq_vs_single(
2351            bessely_seq,
2352            bessely,
2353            -3.7,
2354            z,
2355            3,
2356            Scaling::Unscaled,
2357            TOL,
2358            "Y all neg",
2359        );
2360    }
2361
2362    #[test]
2363    fn bessely_seq_crossing_nonint() {
2364        let z = Complex64::new(1.5, 0.5);
2365        check_seq_vs_single(
2366            bessely_seq,
2367            bessely,
2368            -1.5,
2369            z,
2370            5,
2371            Scaling::Unscaled,
2372            TOL,
2373            "Y crossing",
2374        );
2375    }
2376
2377    #[test]
2378    fn bessely_seq_int_negative() {
2379        let z = Complex64::new(1.5, 0.5);
2380        check_seq_vs_single(
2381            bessely_seq,
2382            bessely,
2383            -3.0,
2384            z,
2385            4,
2386            Scaling::Unscaled,
2387            TOL,
2388            "Y int neg",
2389        );
2390    }
2391
2392    #[test]
2393    fn bessely_seq_int_crossing() {
2394        let z = Complex64::new(1.5, 0.5);
2395        check_seq_vs_single(
2396            bessely_seq,
2397            bessely,
2398            -2.0,
2399            z,
2400            5,
2401            Scaling::Unscaled,
2402            TOL,
2403            "Y int cross",
2404        );
2405    }
2406
2407    // ── I ──
2408
2409    #[test]
2410    fn besseli_seq_all_negative_nonint() {
2411        let z = Complex64::new(1.5, 0.5);
2412        check_seq_vs_single(
2413            besseli_seq,
2414            besseli,
2415            -3.7,
2416            z,
2417            3,
2418            Scaling::Unscaled,
2419            TOL,
2420            "I all neg",
2421        );
2422    }
2423
2424    #[test]
2425    fn besseli_seq_crossing_nonint() {
2426        let z = Complex64::new(1.5, 0.5);
2427        check_seq_vs_single(
2428            besseli_seq,
2429            besseli,
2430            -1.5,
2431            z,
2432            5,
2433            Scaling::Unscaled,
2434            TOL,
2435            "I crossing",
2436        );
2437    }
2438
2439    #[test]
2440    fn besseli_seq_int_negative() {
2441        let z = Complex64::new(1.5, 0.5);
2442        check_seq_vs_single(
2443            besseli_seq,
2444            besseli,
2445            -3.0,
2446            z,
2447            4,
2448            Scaling::Unscaled,
2449            TOL,
2450            "I int neg",
2451        );
2452    }
2453
2454    #[test]
2455    fn besseli_seq_int_crossing() {
2456        let z = Complex64::new(1.5, 0.5);
2457        check_seq_vs_single(
2458            besseli_seq,
2459            besseli,
2460            -2.0,
2461            z,
2462            5,
2463            Scaling::Unscaled,
2464            TOL,
2465            "I int cross",
2466        );
2467    }
2468
2469    // ── Scaled variants ──
2470
2471    #[test]
2472    fn besseli_seq_scaled_negative_nonint() {
2473        let z = Complex64::new(2.0, 1.0);
2474        check_seq_vs_single(
2475            besseli_seq,
2476            besseli_scaled,
2477            -2.3,
2478            z,
2479            5,
2480            Scaling::Exponential,
2481            TOL,
2482            "I scaled neg",
2483        );
2484    }
2485
2486    #[test]
2487    fn besselj_seq_scaled_crossing() {
2488        let z = Complex64::new(2.0, 1.0);
2489        check_seq_vs_single(
2490            besselj_seq,
2491            besselj_scaled,
2492            -1.5,
2493            z,
2494            4,
2495            Scaling::Exponential,
2496            TOL,
2497            "J scaled cross",
2498        );
2499    }
2500
2501    #[test]
2502    fn besselk_seq_scaled_negative() {
2503        let z = Complex64::new(2.0, 1.0);
2504        check_seq_vs_single(
2505            besselk_seq,
2506            besselk_scaled,
2507            -2.5,
2508            z,
2509            4,
2510            Scaling::Exponential,
2511            TOL,
2512            "K scaled neg",
2513        );
2514    }
2515
2516    #[test]
2517    fn hankel1_seq_scaled_negative() {
2518        let z = Complex64::new(2.0, 1.0);
2519        check_seq_vs_single(
2520            hankel1_seq,
2521            hankel1_scaled,
2522            -2.5,
2523            z,
2524            4,
2525            Scaling::Exponential,
2526            TOL,
2527            "H1 scaled neg",
2528        );
2529    }
2530
2531    #[test]
2532    fn bessely_seq_scaled_crossing() {
2533        let z = Complex64::new(2.0, 1.0);
2534        check_seq_vs_single(
2535            bessely_seq,
2536            bessely_scaled,
2537            -1.5,
2538            z,
2539            4,
2540            Scaling::Exponential,
2541            TOL,
2542            "Y scaled cross",
2543        );
2544    }
2545
2546    // ── Edge: -0.0 ──
2547
2548    #[test]
2549    fn besselj_seq_neg_zero() {
2550        let z = Complex64::new(1.0, 0.0);
2551        check_seq_vs_single(
2552            besselj_seq,
2553            besselj,
2554            -0.0,
2555            z,
2556            3,
2557            Scaling::Unscaled,
2558            TOL,
2559            "J neg zero",
2560        );
2561    }
2562}