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) | 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.1", default-features = false }
133//!
134//! # no_std + alloc — full API:
135//! complex-bessel = { version = "0.1", 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 = 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 = 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 = 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 = 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 = 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 = 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::Overflow`] if |z| is too large for a finite result.
579/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
580/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
581#[inline]
582pub fn airy<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
583 let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Unscaled)?;
584 Ok(result)
585}
586
587/// Derivative of the Airy function, Ai'(z).
588///
589/// Computes the derivative of Ai(z) for complex z.
590/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
591/// Satisfies the differential equation Ai′′(z) = z · Ai(z).
592///
593/// # Example
594///
595/// ```
596/// use complex_bessel::airyprime;
597/// use num_complex::Complex;
598///
599/// let z = Complex::new(0.0_f64, 0.0);
600/// let ai_prime = airyprime(z).unwrap();
601/// assert!((ai_prime.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
602/// ```
603///
604/// # Errors
605///
606/// - [`Error::Overflow`] if |z| is too large for a finite result.
607/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
608/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
609#[inline]
610pub fn airyprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
611 let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
612 Ok(result)
613}
614
615/// Airy function of the second kind, Bi(z).
616///
617/// Computes the Airy function Bi(z) for complex z.
618/// Bi(z) is the solution to w′′ − z·w = 0 that grows super-exponentially
619/// for large positive real z.
620///
621/// # Example
622///
623/// ```
624/// use complex_bessel::biry;
625/// use num_complex::Complex;
626///
627/// let z = Complex::new(0.0_f64, 0.0);
628/// let bi = biry(z).unwrap();
629/// assert!((bi.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
630/// ```
631///
632/// # Errors
633///
634/// - [`Error::Overflow`] if |z| is too large for a finite result.
635/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
636/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
637#[inline]
638pub fn biry<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
639 let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Unscaled)?;
640 Ok(result)
641}
642
643/// Derivative of the Airy function of the second kind, Bi'(z).
644///
645/// Computes the derivative of Bi(z) for complex z.
646/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
647/// Satisfies the differential equation Bi′′(z) = z · Bi(z).
648///
649/// # Example
650///
651/// ```
652/// use complex_bessel::biryprime;
653/// use num_complex::Complex;
654///
655/// let z = Complex::new(0.0_f64, 0.0);
656/// let bi_prime = biryprime(z).unwrap();
657/// assert!((bi_prime.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
658/// ```
659///
660/// # Errors
661///
662/// - [`Error::Overflow`] if |z| is too large for a finite result.
663/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
664/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
665#[inline]
666pub fn biryprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
667 let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
668 Ok(result)
669}
670
671// ── Scaled single-value functions ──
672
673/// Scaled Bessel function of the first kind, exp(−|Im(z)|) · J<sub>ν</sub>(z).
674///
675/// The exponential factor cancels the asymptotic growth of J for large imaginary
676/// arguments, keeping results in a representable floating-point range.
677///
678/// Supports negative ν via the same reflection formula as [`besselj`].
679///
680/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
681///
682/// # Example
683///
684/// ```
685/// use complex_bessel::besselj_scaled;
686/// use num_complex::Complex;
687///
688/// let z = Complex::new(0.0_f64, 10.0);
689/// let j_s = besselj_scaled(0.0, z).unwrap();
690/// assert!((j_s.re - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * J_0(10i) ≈ 0.1278
691/// ```
692///
693/// # Errors
694///
695/// - [`Error::InvalidInput`] if z = 0 and ν is a negative non-integer.
696/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
697/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
698/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
699#[inline]
700pub fn besselj_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
701 besselj_internal(nu, z, Scaling::Exponential)
702}
703
704/// Scaled Bessel function of the second kind, exp(−|Im(z)|) · Y<sub>ν</sub>(z).
705///
706/// The exponential factor cancels the asymptotic growth of Y for large imaginary
707/// arguments, keeping results in a representable floating-point range.
708///
709/// Supports negative ν via the same reflection formula as [`bessely`].
710///
711/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
712///
713/// # Example
714///
715/// ```
716/// use complex_bessel::bessely_scaled;
717/// use num_complex::Complex;
718///
719/// let z = Complex::new(0.0_f64, 10.0);
720/// let y_s = bessely_scaled(0.0, z).unwrap();
721/// assert!((y_s.im - 0.1278).abs() < 1e-3); // exp(-|Im(z)|) * Y_0(10i), Im ≈ 0.1278
722/// ```
723///
724/// # Errors
725///
726/// - [`Error::InvalidInput`] if z = 0.
727/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
728/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
729/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
730#[inline]
731pub fn bessely_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
732 bessely_internal(nu, z, Scaling::Exponential)
733}
734
735/// Scaled modified Bessel function of the first kind, exp(−|Re(z)|) · I<sub>ν</sub>(z).
736///
737/// I_ν(z) grows exponentially for large |Re(z)|, so the unscaled value can
738/// easily overflow. The scaling factor exp(−|Re(z)|) keeps the result finite.
739///
740/// Supports negative ν via the same reflection formula as [`besseli`].
741///
742/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
743///
744/// # Example
745///
746/// ```
747/// use complex_bessel::besseli_scaled;
748/// use num_complex::Complex;
749///
750/// let z = Complex::new(10.0_f64, 0.0);
751/// let i_s = besseli_scaled(0.0, z).unwrap();
752/// assert!((i_s.re - 0.1278).abs() < 1e-3); // exp(-10) * I_0(10) ≈ 0.1278
753/// ```
754///
755/// # Errors
756///
757/// - [`Error::InvalidInput`] if z = 0 and ν is a negative non-integer.
758/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
759/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
760/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
761#[inline]
762pub fn besseli_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
763 besseli_internal(nu, z, Scaling::Exponential)
764}
765
766/// Scaled modified Bessel function of the second kind, exp(z) · K<sub>ν</sub>(z).
767///
768/// K_ν(z) decays exponentially for large Re(z), so unscaled values can underflow
769/// to zero. The scaling factor exp(z) keeps the result in a normal range.
770///
771/// Supports negative ν via the same reflection formula as [`besselk`].
772///
773/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
774///
775/// # Example
776///
777/// ```
778/// use complex_bessel::besselk_scaled;
779/// use num_complex::Complex;
780///
781/// let z = Complex::new(500.0_f64, 0.0);
782///
783/// // K_0(500) would underflow to 0, but the scaled version stays finite:
784/// let k_s = besselk_scaled(0.0, z).unwrap();
785/// assert!((k_s.re - 0.0560).abs() < 1e-3); // exp(500) * K_0(500) ≈ 0.0560
786/// ```
787///
788/// # Errors
789///
790/// - [`Error::InvalidInput`] if z = 0.
791/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
792/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
793/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
794#[inline]
795pub fn besselk_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
796 besselk_internal(nu, z, Scaling::Exponential)
797}
798
799/// Scaled Hankel function of the first kind, exp(−iz) · H<sub>ν</sub><sup>(1)</sup>(z).
800///
801/// H^(1) grows exponentially in the lower half-plane;
802/// the scaling factor removes this growth, preventing overflow.
803///
804/// Supports negative ν via the same reflection formula as [`hankel1`].
805///
806/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
807///
808/// # Example
809///
810/// ```
811/// use complex_bessel::hankel1_scaled;
812/// use num_complex::Complex;
813///
814/// let z = Complex::new(1.0_f64, 0.0);
815/// let h_s = hankel1_scaled(0.0, z).unwrap();
816/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(-i) * H^(1)_0(1), Re ≈ 0.4877
817/// ```
818///
819/// # Errors
820///
821/// - [`Error::InvalidInput`] if z = 0.
822/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
823/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
824/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
825#[inline]
826pub fn hankel1_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
827 hankel_internal(HankelKind::First, nu, z, Scaling::Exponential)
828}
829
830/// Scaled Hankel function of the second kind, exp(iz) · H<sub>ν</sub><sup>(2)</sup>(z).
831///
832/// H^(2) grows exponentially in the upper half-plane;
833/// the scaling factor removes this growth, preventing overflow.
834///
835/// Supports negative ν via the same reflection formula as [`hankel2`].
836///
837/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
838///
839/// # Example
840///
841/// ```
842/// use complex_bessel::hankel2_scaled;
843/// use num_complex::Complex;
844///
845/// let z = Complex::new(1.0_f64, 0.0);
846/// let h_s = hankel2_scaled(0.0, z).unwrap();
847/// assert!((h_s.re - 0.4877).abs() < 1e-3); // exp(i) * H^(2)_0(1), Re ≈ 0.4877
848/// ```
849///
850/// # Errors
851///
852/// - [`Error::InvalidInput`] if z = 0.
853/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
854/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
855/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
856#[inline]
857pub fn hankel2_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
858 hankel_internal(HankelKind::Second, nu, z, Scaling::Exponential)
859}
860
861/// Scaled Airy function, exp(ζ) · Ai(z), where ζ = (2/3) z√z.
862///
863/// Ai(z) decays super-exponentially for large positive real z.
864/// The scaling factor exp(ζ) keeps the result representable.
865///
866/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
867///
868/// # Example
869///
870/// ```
871/// use complex_bessel::airy_scaled;
872/// use num_complex::Complex;
873///
874/// let z = Complex::new(5.0_f64, 0.0);
875/// let ai_s = airy_scaled(z).unwrap();
876/// assert!((ai_s.re - 0.1870).abs() < 1e-3); // exp(ζ) * Ai(5) ≈ 0.1870
877/// ```
878///
879/// # Errors
880///
881/// - [`Error::Overflow`] if |z| is too large for a finite result.
882/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
883/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
884#[inline]
885pub fn airy_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
886 let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Exponential)?;
887 Ok(result)
888}
889
890/// Scaled derivative of the Airy function, exp(ζ) · Ai'(z), where ζ = (2/3) z√z.
891///
892/// Ai'(z) decays super-exponentially for large positive real z, just as Ai(z) does.
893/// The scaling factor exp(ζ) keeps the result representable.
894///
895/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
896///
897/// # Example
898///
899/// ```
900/// use complex_bessel::airyprime_scaled;
901/// use num_complex::Complex;
902///
903/// let z = Complex::new(5.0_f64, 0.0);
904/// let ai_s = airyprime_scaled(z).unwrap();
905/// assert!((ai_s.re + 0.4270).abs() < 1e-3); // exp(ζ) * Ai'(5) ≈ -0.4270
906/// ```
907///
908/// # Errors
909///
910/// - [`Error::Overflow`] if |z| is too large for a finite result.
911/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
912/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
913#[inline]
914pub fn airyprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
915 let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Exponential)?;
916 Ok(result)
917}
918
919/// Scaled Airy function of the second kind, exp(−|Re(ζ)|) · Bi(z), where ζ = (2/3) z√z.
920///
921/// Bi(z) grows super-exponentially for large positive real z.
922/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
923///
924/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
925///
926/// # Example
927///
928/// ```
929/// use complex_bessel::biry_scaled;
930/// use num_complex::Complex;
931///
932/// let z = Complex::new(5.0_f64, 0.0);
933/// let bi_s = biry_scaled(z).unwrap();
934/// assert!((bi_s.re - 0.3811).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi(5) ≈ 0.3811
935/// ```
936///
937/// # Errors
938///
939/// - [`Error::Overflow`] if |z| is too large for a finite result.
940/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
941/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
942#[inline]
943pub fn biry_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
944 let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Exponential)?;
945 Ok(result)
946}
947
948/// Scaled derivative of the Airy function of the second kind, exp(−|Re(ζ)|) · Bi'(z), where ζ = (2/3) z√z.
949///
950/// Bi'(z) grows super-exponentially for large positive real z, just as Bi(z) does.
951/// The scaling factor exp(−|Re(ζ)|) keeps the result representable.
952///
953/// See [crate-level docs](crate#exponential-scaling) for the full scaling table.
954///
955/// # Example
956///
957/// ```
958/// use complex_bessel::biryprime_scaled;
959/// use num_complex::Complex;
960///
961/// let z = Complex::new(5.0_f64, 0.0);
962/// let bi_s = biryprime_scaled(z).unwrap();
963/// assert!((bi_s.re - 0.8319).abs() < 1e-3); // exp(-|Re(ζ)|) * Bi'(5) ≈ 0.8319
964/// ```
965///
966/// # Errors
967///
968/// - [`Error::Overflow`] if |z| is too large for a finite result.
969/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
970/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
971#[inline]
972pub fn biryprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
973 let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Exponential)?;
974 Ok(result)
975}
976
977// ── Airy _raw functions (expose Accuracy) ──
978
979/// Airy function Ai(z) with accuracy status.
980///
981/// Like [`airy`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
982/// - [`Accuracy::Normal`] — no significant precision loss
983/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
984///
985/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
986/// see [crate-level docs](crate#exponential-scaling) for details.
987///
988/// # Example
989///
990/// ```
991/// use complex_bessel::*;
992/// use num_complex::Complex;
993///
994/// let z = Complex::new(0.0_f64, 0.0);
995/// let result = airy_raw(z, Scaling::Unscaled).unwrap();
996/// assert!((result.value.re - 0.3550).abs() < 1e-3); // Ai(0) ≈ 0.3550
997/// assert!(matches!(result.status, Accuracy::Normal));
998/// ```
999///
1000/// # Errors
1001///
1002/// - [`Error::Overflow`] if |z| is too large for a finite result.
1003/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1004/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1005#[inline]
1006pub fn airy_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1007 let (value, _nz, status) = airy::zairy(z, AiryDerivative::Value, scaling)?;
1008 Ok(AiryResult { value, status })
1009}
1010
1011/// Derivative of the Airy function Ai'(z) with accuracy status.
1012///
1013/// Like [`airyprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1014/// - [`Accuracy::Normal`] — no significant precision loss
1015/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1016///
1017/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1018/// see [crate-level docs](crate#exponential-scaling) for details.
1019///
1020/// # Example
1021///
1022/// ```
1023/// use complex_bessel::*;
1024/// use num_complex::Complex;
1025///
1026/// let z = Complex::new(0.0_f64, 0.0);
1027/// let result = airyprime_raw(z, Scaling::Unscaled).unwrap();
1028/// assert!((result.value.re + 0.2588).abs() < 1e-3); // Ai'(0) ≈ -0.2588
1029/// assert!(matches!(result.status, Accuracy::Normal));
1030/// ```
1031///
1032/// # Errors
1033///
1034/// - [`Error::Overflow`] if |z| is too large for a finite result.
1035/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1036/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1037#[inline]
1038pub fn airyprime_raw<T: BesselFloat>(
1039 z: Complex<T>,
1040 scaling: Scaling,
1041) -> Result<AiryResult<T>, Error> {
1042 let (value, _nz, status) = airy::zairy(z, AiryDerivative::Derivative, scaling)?;
1043 Ok(AiryResult { value, status })
1044}
1045
1046/// Airy function of the second kind Bi(z) with accuracy status.
1047///
1048/// Like [`biry`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1049/// - [`Accuracy::Normal`] — no significant precision loss
1050/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1051///
1052/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1053/// see [crate-level docs](crate#exponential-scaling) for details.
1054///
1055/// # Example
1056///
1057/// ```
1058/// use complex_bessel::*;
1059/// use num_complex::Complex;
1060///
1061/// let z = Complex::new(0.0_f64, 0.0);
1062/// let result = biry_raw(z, Scaling::Unscaled).unwrap();
1063/// assert!((result.value.re - 0.6149).abs() < 1e-3); // Bi(0) ≈ 0.6149
1064/// assert!(matches!(result.status, Accuracy::Normal));
1065/// ```
1066///
1067/// # Errors
1068///
1069/// - [`Error::Overflow`] if |z| is too large for a finite result.
1070/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1071/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1072#[inline]
1073pub fn biry_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
1074 let (value, status) = airy::zbiry(z, AiryDerivative::Value, scaling)?;
1075 Ok(AiryResult { value, status })
1076}
1077
1078/// Derivative of the Airy function of the second kind Bi'(z) with accuracy status.
1079///
1080/// Like [`biryprime`], but returns an [`AiryResult`] that includes an [`Accuracy`] status:
1081/// - [`Accuracy::Normal`] — no significant precision loss
1082/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| very large)
1083///
1084/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1085/// see [crate-level docs](crate#exponential-scaling) for details.
1086///
1087/// # Example
1088///
1089/// ```
1090/// use complex_bessel::*;
1091/// use num_complex::Complex;
1092///
1093/// let z = Complex::new(0.0_f64, 0.0);
1094/// let result = biryprime_raw(z, Scaling::Unscaled).unwrap();
1095/// assert!((result.value.re - 0.4483).abs() < 1e-3); // Bi'(0) ≈ 0.4483
1096/// assert!(matches!(result.status, Accuracy::Normal));
1097/// ```
1098///
1099/// # Errors
1100///
1101/// - [`Error::Overflow`] if |z| is too large for a finite result.
1102/// - [`Error::TotalPrecisionLoss`] if |z| is too large for any significant digits (roughly > 10⁶ for f64).
1103/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1104#[inline]
1105pub fn biryprime_raw<T: BesselFloat>(
1106 z: Complex<T>,
1107 scaling: Scaling,
1108) -> Result<AiryResult<T>, Error> {
1109 let (value, status) = airy::zbiry(z, AiryDerivative::Derivative, scaling)?;
1110 Ok(AiryResult { value, status })
1111}
1112
1113// ── Sequence functions with scaling option (require alloc) ──
1114
1115#[cfg(feature = "alloc")]
1116extern crate alloc as alloc_crate;
1117
1118#[cfg(feature = "alloc")]
1119fn seq_helper<T: BesselFloat>(
1120 n: usize,
1121 f: impl FnOnce(&mut [Complex<T>]) -> Result<(usize, Accuracy), Error>,
1122) -> Result<BesselResult<T>, Error> {
1123 let zero = T::zero();
1124 let mut values = alloc_crate::vec![Complex::new(zero, zero); n];
1125 let (underflow_count, status) = f(&mut values)?;
1126 Ok(BesselResult {
1127 values,
1128 underflow_count,
1129 status,
1130 })
1131}
1132
1133/// Take the worse of two statuses (Reduced > Normal).
1134#[cfg(feature = "alloc")]
1135#[inline]
1136fn worse_status(a: Accuracy, b: Accuracy) -> Accuracy {
1137 match (a, b) {
1138 (Accuracy::Reduced, _) | (_, Accuracy::Reduced) => Accuracy::Reduced,
1139 _ => Accuracy::Normal,
1140 }
1141}
1142
1143/// Recount underflow: number of leading zero elements in the output.
1144#[cfg(feature = "alloc")]
1145#[inline]
1146fn recount_underflow<T: BesselFloat>(values: &[Complex<T>]) -> usize {
1147 let zero = Complex::new(T::zero(), T::zero());
1148 values.iter().take_while(|v| **v == zero).count()
1149}
1150
1151/// Compute the number of negative-order elements (before crossing zero).
1152/// For orders ν, ν+1, …, ν+n−1, this is the count where ν+j < 0.
1153#[cfg(feature = "alloc")]
1154#[inline]
1155fn neg_count<T: BesselFloat>(nu: T, n: usize) -> usize {
1156 if nu >= T::zero() {
1157 return 0;
1158 }
1159 // Number of j in [0,n) with nu + j < 0
1160 let abs_nu = nu.abs();
1161 // ceil(|nu|) gives the first j where nu+j >= 0
1162 let c = abs_nu.ceil();
1163 let cn = if let Some(v) = c.to_usize() { v } else { n };
1164 cn.min(n)
1165}
1166
1167// ── Negative-order _seq implementations ──
1168
1169/// K_seq with negative order support: K_{-ν} = K_ν (even function).
1170///
1171/// Orders: ν, ν+1, …, ν+n−1. Negative orders map to |order|.
1172/// - All negative: single call zbesk(|ν|-(n-1), n), reverse.
1173/// - All positive: single call zbesk(ν, n).
1174/// - Integer crossing: single call covering max(|ν|, ν+n-1), mirror.
1175/// - Non-integer crossing: two calls (neg lattice + pos lattice).
1176#[cfg(feature = "alloc")]
1177#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1178fn besselk_seq_neg<T: BesselFloat>(
1179 nu: T,
1180 z: Complex<T>,
1181 n: usize,
1182 scaling: Scaling,
1183) -> Result<BesselResult<T>, Error> {
1184 let zero = T::zero();
1185 let czero = Complex::new(zero, zero);
1186 let abs_nu = nu.abs();
1187 let nc = neg_count(nu, n);
1188
1189 // All negative: absolute values are |ν|, |ν|-1, …, |ν|-(nc-1) (descending)
1190 // = ascending from |ν|-(nc-1) to |ν|
1191 if nc == n {
1192 // All orders negative, e.g., ν=-3.7, n=3 → orders -3.7,-2.7,-1.7
1193 // |orders| = 3.7, 2.7, 1.7 → start at 1.7, compute 3 ascending → reverse
1194 let start = abs_nu - T::from_f64((n - 1) as f64);
1195 let mut buf = alloc_crate::vec![czero; n];
1196 let (_, status) = besk::zbesk(z, start, scaling, &mut buf)?;
1197 buf.reverse();
1198 let uc = recount_underflow(&buf);
1199 return Ok(BesselResult {
1200 values: buf,
1201 underflow_count: uc,
1202 status,
1203 });
1204 }
1205
1206 // Check if integer crossing (fractional part is 0 for integer nu)
1207 let is_int = as_integer(abs_nu).is_some();
1208
1209 if is_int {
1210 // Integer crossing: e.g., ν=-2, n=5 → orders -2,-1,0,1,2
1211 // All have same fractional part (0). One call covers all |orders|.
1212 // Max absolute order = max(|ν|, ν+n-1)
1213 let last_order = nu + T::from_f64((n - 1) as f64);
1214 let max_abs = if abs_nu > last_order {
1215 abs_nu
1216 } else {
1217 last_order
1218 };
1219 let buf_n = max_abs.to_usize().unwrap_or(0) + 1; // orders 0..=max_abs
1220 let mut buf = alloc_crate::vec![czero; buf_n];
1221 let (_, status) = besk::zbesk(z, zero, scaling, &mut buf)?;
1222
1223 let mut values = alloc_crate::vec![czero; n];
1224 for j in 0..n {
1225 let order = nu + T::from_f64(j as f64);
1226 let idx = order.abs().to_usize().unwrap_or(0);
1227 values[j] = buf[idx];
1228 }
1229 let uc = recount_underflow(&values);
1230 return Ok(BesselResult {
1231 values,
1232 underflow_count: uc,
1233 status,
1234 });
1235 }
1236
1237 // Non-integer crossing: two separate lattices
1238 // Negative part: orders ν, ν+1, …, ν+(nc-1) → |orders| descending
1239 // frac_neg = fractional part of |ν|
1240 let frac_neg = abs_nu - abs_nu.floor();
1241 let neg_start = frac_neg; // smallest |order| in negative lattice
1242 let mut neg_buf = alloc_crate::vec![czero; nc];
1243 let (_, status1) = besk::zbesk(z, neg_start, scaling, &mut neg_buf)?;
1244
1245 // Positive part: orders ν+nc, ν+nc+1, …, ν+n-1
1246 let pos_start = nu + T::from_f64(nc as f64);
1247 let pos_n = n - nc;
1248 let mut pos_buf = alloc_crate::vec![czero; pos_n];
1249 let (_, status2) = besk::zbesk(z, pos_start, scaling, &mut pos_buf)?;
1250
1251 let mut values = alloc_crate::vec![czero; n];
1252 // Negative part: buf[0]=K(frac_neg), buf[1]=K(frac_neg+1), …
1253 // Need to map: order ν+j → |ν+j| → index in neg_buf
1254 // |ν+j| = abs_nu - j, and neg_buf[k] = K(frac_neg + k)
1255 // So k = |ν+j| - frac_neg = (abs_nu - j) - frac_neg = floor(abs_nu) - j
1256 for j in 0..nc {
1257 let k = (abs_nu - T::from_f64(j as f64))
1258 .floor()
1259 .to_usize()
1260 .unwrap_or(0);
1261 values[j] = neg_buf[k];
1262 }
1263 // Positive part: direct copy
1264 for j in 0..pos_n {
1265 values[nc + j] = pos_buf[j];
1266 }
1267
1268 let status = worse_status(status1, status2);
1269 let uc = recount_underflow(&values);
1270 Ok(BesselResult {
1271 values,
1272 underflow_count: uc,
1273 status,
1274 })
1275}
1276
1277/// H_seq with negative order support: H^(m)_{-ν} = exp(±νπi)·H^(m)_ν.
1278///
1279/// Same lattice-splitting as K, but negative-order elements get rotation.
1280#[cfg(feature = "alloc")]
1281#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1282fn hankel_seq_neg<T: BesselFloat>(
1283 kind: HankelKind,
1284 nu: T,
1285 z: Complex<T>,
1286 n: usize,
1287 scaling: Scaling,
1288) -> Result<BesselResult<T>, Error> {
1289 let zero = T::zero();
1290 let czero = Complex::new(zero, zero);
1291 let abs_nu = nu.abs();
1292 let nc = neg_count(nu, n);
1293 let is_int = as_integer(abs_nu).is_some();
1294
1295 if nc == n {
1296 // All negative: compute at positive |orders| then rotate each
1297 let start = abs_nu - T::from_f64((n - 1) as f64);
1298 let mut buf = alloc_crate::vec![czero; n];
1299 let (_, status) = besh::zbesh(z, start, kind, scaling, &mut buf)?;
1300 buf.reverse();
1301 // Apply rotation: element j has |order| = abs_nu - j
1302 for j in 0..n {
1303 let order = abs_nu - T::from_f64(j as f64);
1304 buf[j] = reflect_h_element(order, kind, buf[j]);
1305 }
1306 let uc = recount_underflow(&buf);
1307 return Ok(BesselResult {
1308 values: buf,
1309 underflow_count: uc,
1310 status,
1311 });
1312 }
1313
1314 if is_int {
1315 // Integer crossing: single call, rotate negative elements
1316 let last_order = nu + T::from_f64((n - 1) as f64);
1317 let max_abs = if abs_nu > last_order {
1318 abs_nu
1319 } else {
1320 last_order
1321 };
1322 let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1323 let mut buf = alloc_crate::vec![czero; buf_n];
1324 let (_, status) = besh::zbesh(z, zero, kind, scaling, &mut buf)?;
1325
1326 let mut values = alloc_crate::vec![czero; n];
1327 for j in 0..n {
1328 let order = nu + T::from_f64(j as f64);
1329 let idx = order.abs().to_usize().unwrap_or(0);
1330 values[j] = buf[idx];
1331 if order < zero {
1332 values[j] = reflect_h_element(order.abs(), kind, values[j]);
1333 }
1334 }
1335 let uc = recount_underflow(&values);
1336 return Ok(BesselResult {
1337 values,
1338 underflow_count: uc,
1339 status,
1340 });
1341 }
1342
1343 // Non-integer crossing: two lattices
1344 let frac_neg = abs_nu - abs_nu.floor();
1345 let mut neg_buf = alloc_crate::vec![czero; nc];
1346 let (_, status1) = besh::zbesh(z, frac_neg, kind, scaling, &mut neg_buf)?;
1347
1348 let pos_start = nu + T::from_f64(nc as f64);
1349 let pos_n = n - nc;
1350 let mut pos_buf = alloc_crate::vec![czero; pos_n];
1351 let (_, status2) = besh::zbesh(z, pos_start, kind, scaling, &mut pos_buf)?;
1352
1353 let mut values = alloc_crate::vec![czero; n];
1354 for j in 0..nc {
1355 let abs_order = abs_nu - T::from_f64(j as f64);
1356 let k = abs_order.floor().to_usize().unwrap_or(0);
1357 values[j] = reflect_h_element(abs_order, kind, neg_buf[k]);
1358 }
1359 for j in 0..pos_n {
1360 values[nc + j] = pos_buf[j];
1361 }
1362
1363 let status = worse_status(status1, status2);
1364 let uc = recount_underflow(&values);
1365 Ok(BesselResult {
1366 values,
1367 underflow_count: uc,
1368 status,
1369 })
1370}
1371
1372/// J_seq with negative order support.
1373/// J_{-ν} = cos(νπ)·J_ν − sin(νπ)·Y_ν (non-integer)
1374/// J_{-n} = (-1)^n · J_n (integer)
1375#[cfg(feature = "alloc")]
1376#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1377fn besselj_seq_neg<T: BesselFloat>(
1378 nu: T,
1379 z: Complex<T>,
1380 n: usize,
1381 scaling: Scaling,
1382) -> Result<BesselResult<T>, Error> {
1383 let zero = T::zero();
1384 let czero = Complex::new(zero, zero);
1385 let abs_nu = nu.abs();
1386 let nc = neg_count(nu, n);
1387 let is_int = as_integer(abs_nu).is_some();
1388
1389 if is_int {
1390 // Integer: J_{-n}(z) = (-1)^n J_n(z). Same lattice.
1391 let last_order = nu + T::from_f64((n - 1) as f64);
1392 let max_abs = if abs_nu > last_order {
1393 abs_nu
1394 } else {
1395 last_order
1396 };
1397 let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1398 let mut buf = alloc_crate::vec![czero; buf_n];
1399 let (_, status) = besj::zbesj(z, zero, scaling, &mut buf)?;
1400
1401 let mut values = alloc_crate::vec![czero; n];
1402 for j in 0..n {
1403 let order = nu + T::from_f64(j as f64);
1404 let idx = order.abs().to_usize().unwrap_or(0);
1405 values[j] = buf[idx];
1406 if order < zero {
1407 let int_order = order.abs().to_i64().unwrap_or(0);
1408 values[j] = values[j] * integer_sign::<T>(int_order);
1409 }
1410 }
1411 let uc = recount_underflow(&values);
1412 return Ok(BesselResult {
1413 values,
1414 underflow_count: uc,
1415 status,
1416 });
1417 }
1418
1419 if nc == n {
1420 // All negative, non-integer: need J and Y at positive |orders|
1421 let start = abs_nu - T::from_f64((n - 1) as f64);
1422 let mut j_buf = alloc_crate::vec![czero; n];
1423 let mut y_buf = alloc_crate::vec![czero; n];
1424 let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
1425 let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
1426
1427 let mut values = alloc_crate::vec![czero; n];
1428 // buf[k] = f(start+k). For output j, |order| = abs_nu - j, k = |order| - start = n-1-j
1429 for j in 0..n {
1430 let k = n - 1 - j;
1431 let order = abs_nu - T::from_f64(j as f64);
1432 values[j] = reflect_j_element(order, j_buf[k], y_buf[k]);
1433 }
1434 let uc = recount_underflow(&values);
1435 return Ok(BesselResult {
1436 values,
1437 underflow_count: uc,
1438 status: worse_status(s1, s2),
1439 });
1440 }
1441
1442 // Non-integer crossing: two lattices
1443 let frac_neg = abs_nu - abs_nu.floor();
1444 let mut neg_j = alloc_crate::vec![czero; nc];
1445 let mut neg_y = alloc_crate::vec![czero; nc];
1446 let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
1447 let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
1448
1449 let pos_start = nu + T::from_f64(nc as f64);
1450 let pos_n = n - nc;
1451 let mut pos_buf = alloc_crate::vec![czero; pos_n];
1452 let (_, s3) = besj::zbesj(z, pos_start, scaling, &mut pos_buf)?;
1453
1454 let mut values = alloc_crate::vec![czero; n];
1455 for j in 0..nc {
1456 let abs_order = abs_nu - T::from_f64(j as f64);
1457 let k = abs_order.floor().to_usize().unwrap_or(0);
1458 values[j] = reflect_j_element(abs_order, neg_j[k], neg_y[k]);
1459 }
1460 for j in 0..pos_n {
1461 values[nc + j] = pos_buf[j];
1462 }
1463
1464 let status = worse_status(worse_status(s1, s2), s3);
1465 let uc = recount_underflow(&values);
1466 Ok(BesselResult {
1467 values,
1468 underflow_count: uc,
1469 status,
1470 })
1471}
1472
1473/// Y_seq with negative order support.
1474/// Y_{-ν} = sin(νπ)·J_ν + cos(νπ)·Y_ν (non-integer)
1475/// Y_{-n} = (-1)^n · Y_n (integer)
1476#[cfg(feature = "alloc")]
1477#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1478fn bessely_seq_neg<T: BesselFloat>(
1479 nu: T,
1480 z: Complex<T>,
1481 n: usize,
1482 scaling: Scaling,
1483) -> Result<BesselResult<T>, Error> {
1484 let zero = T::zero();
1485 let czero = Complex::new(zero, zero);
1486 let abs_nu = nu.abs();
1487 let nc = neg_count(nu, n);
1488 let is_int = as_integer(abs_nu).is_some();
1489
1490 if is_int {
1491 let last_order = nu + T::from_f64((n - 1) as f64);
1492 let max_abs = if abs_nu > last_order {
1493 abs_nu
1494 } else {
1495 last_order
1496 };
1497 let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1498 let mut buf = alloc_crate::vec![czero; buf_n];
1499 let (_, status) = besy::zbesy(z, zero, scaling, &mut buf)?;
1500
1501 let mut values = alloc_crate::vec![czero; n];
1502 for j in 0..n {
1503 let order = nu + T::from_f64(j as f64);
1504 let idx = order.abs().to_usize().unwrap_or(0);
1505 values[j] = buf[idx];
1506 if order < zero {
1507 let int_order = order.abs().to_i64().unwrap_or(0);
1508 values[j] = values[j] * integer_sign::<T>(int_order);
1509 }
1510 }
1511 let uc = recount_underflow(&values);
1512 return Ok(BesselResult {
1513 values,
1514 underflow_count: uc,
1515 status,
1516 });
1517 }
1518
1519 if nc == n {
1520 let start = abs_nu - T::from_f64((n - 1) as f64);
1521 let mut j_buf = alloc_crate::vec![czero; n];
1522 let mut y_buf = alloc_crate::vec![czero; n];
1523 let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
1524 let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
1525
1526 let mut values = alloc_crate::vec![czero; n];
1527 for j in 0..n {
1528 let k = n - 1 - j;
1529 let order = abs_nu - T::from_f64(j as f64);
1530 values[j] = reflect_y_element(order, j_buf[k], y_buf[k]);
1531 }
1532 let uc = recount_underflow(&values);
1533 return Ok(BesselResult {
1534 values,
1535 underflow_count: uc,
1536 status: worse_status(s1, s2),
1537 });
1538 }
1539
1540 // Non-integer crossing: two lattices
1541 let frac_neg = abs_nu - abs_nu.floor();
1542 let mut neg_j = alloc_crate::vec![czero; nc];
1543 let mut neg_y = alloc_crate::vec![czero; nc];
1544 let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
1545 let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
1546
1547 let pos_start = nu + T::from_f64(nc as f64);
1548 let pos_n = n - nc;
1549 let mut pos_buf = alloc_crate::vec![czero; pos_n];
1550 let (_, s3) = besy::zbesy(z, pos_start, scaling, &mut pos_buf)?;
1551
1552 let mut values = alloc_crate::vec![czero; n];
1553 for j in 0..nc {
1554 let abs_order = abs_nu - T::from_f64(j as f64);
1555 let k = abs_order.floor().to_usize().unwrap_or(0);
1556 values[j] = reflect_y_element(abs_order, neg_j[k], neg_y[k]);
1557 }
1558 for j in 0..pos_n {
1559 values[nc + j] = pos_buf[j];
1560 }
1561
1562 let status = worse_status(worse_status(s1, s2), s3);
1563 let uc = recount_underflow(&values);
1564 Ok(BesselResult {
1565 values,
1566 underflow_count: uc,
1567 status,
1568 })
1569}
1570
1571/// I_seq with negative order support.
1572/// I_{-ν} = I_ν + (2/π)sin(νπ)K_ν (non-integer)
1573/// I_{-n} = I_n (integer)
1574#[cfg(feature = "alloc")]
1575#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
1576fn besseli_seq_neg<T: BesselFloat>(
1577 nu: T,
1578 z: Complex<T>,
1579 n: usize,
1580 scaling: Scaling,
1581) -> Result<BesselResult<T>, Error> {
1582 let zero = T::zero();
1583 let czero = Complex::new(zero, zero);
1584 let abs_nu = nu.abs();
1585 let nc = neg_count(nu, n);
1586 let is_int = as_integer(abs_nu).is_some();
1587
1588 if is_int {
1589 // I_{-n} = I_n. Same lattice, no correction needed.
1590 let last_order = nu + T::from_f64((n - 1) as f64);
1591 let max_abs = if abs_nu > last_order {
1592 abs_nu
1593 } else {
1594 last_order
1595 };
1596 let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
1597 let mut buf = alloc_crate::vec![czero; buf_n];
1598 let (_, status) = besi::zbesi(z, zero, scaling, &mut buf)?;
1599
1600 let mut values = alloc_crate::vec![czero; n];
1601 for j in 0..n {
1602 let order = nu + T::from_f64(j as f64);
1603 let idx = order.abs().to_usize().unwrap_or(0);
1604 values[j] = buf[idx];
1605 }
1606 let uc = recount_underflow(&values);
1607 return Ok(BesselResult {
1608 values,
1609 underflow_count: uc,
1610 status,
1611 });
1612 }
1613
1614 if nc == n {
1615 // All negative, non-integer: need I and K at positive |orders|
1616 let start = abs_nu - T::from_f64((n - 1) as f64);
1617 let mut i_buf = alloc_crate::vec![czero; n];
1618 let mut k_buf = alloc_crate::vec![czero; n];
1619 let (_, s1) = besi::zbesi(z, start, scaling, &mut i_buf)?;
1620 let (_, s2) = besk::zbesk(z, start, scaling, &mut k_buf)?;
1621
1622 let mut values = alloc_crate::vec![czero; n];
1623 for j in 0..n {
1624 let k_idx = n - 1 - j;
1625 let order = abs_nu - T::from_f64(j as f64);
1626 let mut k_val = k_buf[k_idx];
1627 if scaling == Scaling::Exponential {
1628 k_val = k_to_i_scaling_correction(z, k_val);
1629 }
1630 values[j] = reflect_i_element(order, i_buf[k_idx], k_val);
1631 }
1632 let uc = recount_underflow(&values);
1633 return Ok(BesselResult {
1634 values,
1635 underflow_count: uc,
1636 status: worse_status(s1, s2),
1637 });
1638 }
1639
1640 // Non-integer crossing: two lattices
1641 let frac_neg = abs_nu - abs_nu.floor();
1642 let mut neg_i = alloc_crate::vec![czero; nc];
1643 let mut neg_k = alloc_crate::vec![czero; nc];
1644 let (_, s1) = besi::zbesi(z, frac_neg, scaling, &mut neg_i)?;
1645 let (_, s2) = besk::zbesk(z, frac_neg, scaling, &mut neg_k)?;
1646
1647 let pos_start = nu + T::from_f64(nc as f64);
1648 let pos_n = n - nc;
1649 let mut pos_buf = alloc_crate::vec![czero; pos_n];
1650 let (_, s3) = besi::zbesi(z, pos_start, scaling, &mut pos_buf)?;
1651
1652 let mut values = alloc_crate::vec![czero; n];
1653 for j in 0..nc {
1654 let abs_order = abs_nu - T::from_f64(j as f64);
1655 let k_idx = abs_order.floor().to_usize().unwrap_or(0);
1656 let mut k_val = neg_k[k_idx];
1657 if scaling == Scaling::Exponential {
1658 k_val = k_to_i_scaling_correction(z, k_val);
1659 }
1660 values[j] = reflect_i_element(abs_order, neg_i[k_idx], k_val);
1661 }
1662 for j in 0..pos_n {
1663 values[nc + j] = pos_buf[j];
1664 }
1665
1666 let status = worse_status(worse_status(s1, s2), s3);
1667 let uc = recount_underflow(&values);
1668 Ok(BesselResult {
1669 values,
1670 underflow_count: uc,
1671 status,
1672 })
1673}
1674
1675#[cfg(feature = "alloc")]
1676/// Compute J<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1677///
1678/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1679/// - [`Accuracy::Normal`] — no significant precision loss
1680/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1681///
1682/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1683/// see [crate-level docs](crate#exponential-scaling) for details.
1684///
1685/// Supports negative ν via the same reflection formula as [`besselj`].
1686///
1687/// # Example
1688///
1689/// ```
1690/// # #[cfg(feature = "alloc")] {
1691/// use complex_bessel::*;
1692/// use num_complex::Complex;
1693///
1694/// let z = Complex::new(1.0_f64, 0.0);
1695///
1696/// // J_0(z), J_1(z), J_2(z) in one call
1697/// let result = besselj_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1698/// assert_eq!(result.values.len(), 3);
1699/// assert!((result.values[0].re - 0.7652).abs() < 1e-3); // J_0(1) ≈ 0.7652
1700/// # }
1701/// ```
1702///
1703/// # Errors
1704///
1705/// - [`Error::InvalidInput`] if n < 1, or if z = 0 and ν is a negative non-integer.
1706/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1707/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1708/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1709pub fn besselj_seq<T: BesselFloat>(
1710 nu: T,
1711 z: Complex<T>,
1712 n: usize,
1713 scaling: Scaling,
1714) -> Result<BesselResult<T>, Error> {
1715 if nu < T::zero() {
1716 return besselj_seq_neg(nu, z, n, scaling);
1717 }
1718 seq_helper(n, |y| besj::zbesj(z, nu, scaling, y))
1719}
1720
1721#[cfg(feature = "alloc")]
1722/// Compute Y<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1723///
1724/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1725/// - [`Accuracy::Normal`] — no significant precision loss
1726/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1727///
1728/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1729/// see [crate-level docs](crate#exponential-scaling) for details.
1730///
1731/// Supports negative ν via the same reflection formula as [`bessely`].
1732///
1733/// # Example
1734///
1735/// ```
1736/// # #[cfg(feature = "alloc")] {
1737/// use complex_bessel::*;
1738/// use num_complex::Complex;
1739///
1740/// let z = Complex::new(1.0_f64, 0.0);
1741///
1742/// // Y_0(z), Y_1(z), Y_2(z) in one call
1743/// let result = bessely_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1744/// assert_eq!(result.values.len(), 3);
1745/// assert!((result.values[0].re - 0.0883).abs() < 1e-3); // Y_0(1) ≈ 0.0883
1746/// # }
1747/// ```
1748///
1749/// # Errors
1750///
1751/// - [`Error::InvalidInput`] if n < 1, or if z = 0.
1752/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1753/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1754/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1755pub fn bessely_seq<T: BesselFloat>(
1756 nu: T,
1757 z: Complex<T>,
1758 n: usize,
1759 scaling: Scaling,
1760) -> Result<BesselResult<T>, Error> {
1761 if nu < T::zero() {
1762 return bessely_seq_neg(nu, z, n, scaling);
1763 }
1764 seq_helper(n, |y| besy::zbesy(z, nu, scaling, y))
1765}
1766
1767#[cfg(feature = "alloc")]
1768/// Compute I<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1769///
1770/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1771/// - [`Accuracy::Normal`] — no significant precision loss
1772/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1773///
1774/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1775/// see [crate-level docs](crate#exponential-scaling) for details.
1776///
1777/// Supports negative ν via the same reflection formula as [`besseli`].
1778///
1779/// # Example
1780///
1781/// ```
1782/// # #[cfg(feature = "alloc")] {
1783/// use complex_bessel::*;
1784/// use num_complex::Complex;
1785///
1786/// let z = Complex::new(1.0_f64, 0.0);
1787///
1788/// // I_0(z), I_1(z), I_2(z) in one call
1789/// let result = besseli_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1790/// assert_eq!(result.values.len(), 3);
1791/// assert!((result.values[0].re - 1.2661).abs() < 1e-3); // I_0(1) ≈ 1.2661
1792/// # }
1793/// ```
1794///
1795/// # Errors
1796///
1797/// - [`Error::InvalidInput`] if n < 1, or if z = 0 and ν is a negative non-integer.
1798/// - [`Error::Overflow`] if |z| or |ν| is too large for a finite result.
1799/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1800/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1801pub fn besseli_seq<T: BesselFloat>(
1802 nu: T,
1803 z: Complex<T>,
1804 n: usize,
1805 scaling: Scaling,
1806) -> Result<BesselResult<T>, Error> {
1807 if nu < T::zero() {
1808 return besseli_seq_neg(nu, z, n, scaling);
1809 }
1810 seq_helper(n, |y| besi::zbesi(z, nu, scaling, y))
1811}
1812
1813#[cfg(feature = "alloc")]
1814/// Compute K<sub>ν+j</sub>(z) for j = 0, 1, …, n−1 in a single call.
1815///
1816/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1817/// - [`Accuracy::Normal`] — no significant precision loss
1818/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1819///
1820/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1821/// see [crate-level docs](crate#exponential-scaling) for details.
1822///
1823/// Supports negative ν via the same reflection formula as [`besselk`].
1824///
1825/// # Example
1826///
1827/// ```
1828/// # #[cfg(feature = "alloc")] {
1829/// use complex_bessel::*;
1830/// use num_complex::Complex;
1831///
1832/// let z = Complex::new(1.0_f64, 0.0);
1833///
1834/// // K_0(z), K_1(z), K_2(z) in one call
1835/// let result = besselk_seq(0.0, z, 3, Scaling::Unscaled).unwrap();
1836/// assert_eq!(result.values.len(), 3);
1837/// assert!((result.values[0].re - 0.4211).abs() < 1e-3); // K_0(1) ≈ 0.4211
1838/// # }
1839/// ```
1840///
1841/// # Errors
1842///
1843/// - [`Error::InvalidInput`] if n < 1, or if z = 0.
1844/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1845/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1846/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1847pub fn besselk_seq<T: BesselFloat>(
1848 nu: T,
1849 z: Complex<T>,
1850 n: usize,
1851 scaling: Scaling,
1852) -> Result<BesselResult<T>, Error> {
1853 if nu < T::zero() {
1854 return besselk_seq_neg(nu, z, n, scaling);
1855 }
1856 seq_helper(n, |y| besk::zbesk(z, nu, scaling, y))
1857}
1858
1859#[cfg(feature = "alloc")]
1860/// Compute H<sub>ν+j</sub><sup>(1)</sup>(z) for j = 0, 1, …, n−1 in a single call.
1861///
1862/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1863/// - [`Accuracy::Normal`] — no significant precision loss
1864/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1865///
1866/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1867/// see [crate-level docs](crate#exponential-scaling) for details.
1868///
1869/// Supports negative ν via the same reflection formula as [`hankel1`].
1870///
1871/// # Example
1872///
1873/// ```
1874/// # #[cfg(feature = "alloc")] {
1875/// use complex_bessel::*;
1876/// use num_complex::Complex;
1877///
1878/// let z = Complex::new(1.0_f64, 0.0);
1879///
1880/// // H^(1)_0(z), H^(1)_1(z) in one call
1881/// let result = hankel1_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
1882/// assert_eq!(result.values.len(), 2);
1883/// assert!((result.values[0].im - 0.0883).abs() < 1e-3); // Im = Y_0(1) ≈ 0.0883
1884/// # }
1885/// ```
1886///
1887/// # Errors
1888///
1889/// - [`Error::InvalidInput`] if n < 1, or if z = 0.
1890/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1891/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1892/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1893pub fn hankel1_seq<T: BesselFloat>(
1894 nu: T,
1895 z: Complex<T>,
1896 n: usize,
1897 scaling: Scaling,
1898) -> Result<BesselResult<T>, Error> {
1899 if nu < T::zero() {
1900 return hankel_seq_neg(HankelKind::First, nu, z, n, scaling);
1901 }
1902 seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::First, scaling, y))
1903}
1904
1905#[cfg(feature = "alloc")]
1906/// Compute H<sub>ν+j</sub><sup>(2)</sup>(z) for j = 0, 1, …, n−1 in a single call.
1907///
1908/// Returns a [`BesselResult`] containing `n` values and an [`Accuracy`]:
1909/// - [`Accuracy::Normal`] — no significant precision loss
1910/// - [`Accuracy::Reduced`] — more than half of significant digits may be lost (|z| or |ν| very large)
1911///
1912/// The `scaling` parameter selects [`Scaling::Unscaled`] or [`Scaling::Exponential`];
1913/// see [crate-level docs](crate#exponential-scaling) for details.
1914///
1915/// Supports negative ν via the same reflection formula as [`hankel2`].
1916///
1917/// # Example
1918///
1919/// ```
1920/// # #[cfg(feature = "alloc")] {
1921/// use complex_bessel::*;
1922/// use num_complex::Complex;
1923///
1924/// let z = Complex::new(1.0_f64, 0.0);
1925///
1926/// // H^(2)_0(z), H^(2)_1(z) in one call
1927/// let result = hankel2_seq(0.0, z, 2, Scaling::Unscaled).unwrap();
1928/// assert_eq!(result.values.len(), 2);
1929/// assert!((result.values[0].im + 0.0883).abs() < 1e-3); // Im = -Y_0(1) ≈ -0.0883
1930/// # }
1931/// ```
1932///
1933/// # Errors
1934///
1935/// - [`Error::InvalidInput`] if n < 1, or if z = 0.
1936/// - [`Error::Overflow`] if |z| is too small or too large for a finite result.
1937/// - [`Error::TotalPrecisionLoss`] if |z| or |ν| is too large for any significant digits (roughly > 10⁹ for f64).
1938/// - [`Error::ConvergenceFailure`] if an internal series or recurrence does not converge (rare).
1939pub fn hankel2_seq<T: BesselFloat>(
1940 nu: T,
1941 z: Complex<T>,
1942 n: usize,
1943 scaling: Scaling,
1944) -> Result<BesselResult<T>, Error> {
1945 if nu < T::zero() {
1946 return hankel_seq_neg(HankelKind::Second, nu, z, n, scaling);
1947 }
1948 seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::Second, scaling, y))
1949}
1950
1951// ── Tests for negative order _seq functions ──
1952
1953#[cfg(all(test, feature = "alloc"))]
1954mod neg_order_seq_tests {
1955 use super::*;
1956 use num_complex::Complex64;
1957
1958 const TOL: f64 = 2e-14;
1959
1960 fn rel_err(a: Complex64, b: Complex64) -> f64 {
1961 let diff = (a - b).norm();
1962 let mag = a.norm().max(b.norm());
1963 if mag == 0.0 { diff } else { diff / mag }
1964 }
1965
1966 /// Verify _seq(ν, z, n)[j] == single_value(ν+j, z) for all j.
1967 fn check_seq_vs_single<F, G>(
1968 seq_fn: F,
1969 single_fn: G,
1970 nu: f64,
1971 z: Complex64,
1972 n: usize,
1973 scaling: Scaling,
1974 tol: f64,
1975 label: &str,
1976 ) where
1977 F: FnOnce(f64, Complex64, usize, Scaling) -> Result<BesselResult<f64>, Error>,
1978 G: Fn(f64, Complex64) -> Result<Complex64, Error>,
1979 {
1980 let result = seq_fn(nu, z, n, scaling).unwrap();
1981 assert_eq!(result.values.len(), n, "{label}: wrong length");
1982 for j in 0..n {
1983 let order = nu + j as f64;
1984 let expected = single_fn(order, z).unwrap();
1985 let err = rel_err(result.values[j], expected);
1986 assert!(
1987 err < tol,
1988 "{label}: order={order}, seq={:?}, single={expected:?}, rel_err={err}",
1989 result.values[j]
1990 );
1991 }
1992 }
1993
1994 // ── K (even function) ──
1995
1996 #[test]
1997 fn besselk_seq_all_negative_nonint() {
1998 let z = Complex64::new(1.5, 0.5);
1999 check_seq_vs_single(
2000 besselk_seq,
2001 |nu, z| besselk(nu, z),
2002 -3.7,
2003 z,
2004 3,
2005 Scaling::Unscaled,
2006 TOL,
2007 "K all neg",
2008 );
2009 }
2010
2011 #[test]
2012 fn besselk_seq_crossing_nonint() {
2013 let z = Complex64::new(1.5, 0.5);
2014 check_seq_vs_single(
2015 besselk_seq,
2016 |nu, z| besselk(nu, z),
2017 -1.5,
2018 z,
2019 5,
2020 Scaling::Unscaled,
2021 TOL,
2022 "K crossing nonint",
2023 );
2024 }
2025
2026 #[test]
2027 fn besselk_seq_int_negative() {
2028 let z = Complex64::new(1.5, 0.5);
2029 check_seq_vs_single(
2030 besselk_seq,
2031 |nu, z| besselk(nu, z),
2032 -3.0,
2033 z,
2034 4,
2035 Scaling::Unscaled,
2036 TOL,
2037 "K int neg",
2038 );
2039 }
2040
2041 #[test]
2042 fn besselk_seq_int_crossing() {
2043 let z = Complex64::new(1.5, 0.5);
2044 check_seq_vs_single(
2045 besselk_seq,
2046 |nu, z| besselk(nu, z),
2047 -2.0,
2048 z,
2049 5,
2050 Scaling::Unscaled,
2051 TOL,
2052 "K int cross",
2053 );
2054 }
2055
2056 #[test]
2057 fn besselk_seq_neg_zero() {
2058 let z = Complex64::new(1.0, 0.0);
2059 check_seq_vs_single(
2060 besselk_seq,
2061 |nu, z| besselk(nu, z),
2062 -0.0,
2063 z,
2064 1,
2065 Scaling::Unscaled,
2066 TOL,
2067 "K neg zero",
2068 );
2069 }
2070
2071 // ── H^(1) ──
2072
2073 #[test]
2074 fn hankel1_seq_all_negative_nonint() {
2075 let z = Complex64::new(1.5, 0.5);
2076 check_seq_vs_single(
2077 hankel1_seq,
2078 |nu, z| hankel1(nu, z),
2079 -3.7,
2080 z,
2081 3,
2082 Scaling::Unscaled,
2083 TOL,
2084 "H1 all neg",
2085 );
2086 }
2087
2088 #[test]
2089 fn hankel1_seq_crossing_nonint() {
2090 let z = Complex64::new(1.5, 0.5);
2091 check_seq_vs_single(
2092 hankel1_seq,
2093 |nu, z| hankel1(nu, z),
2094 -1.5,
2095 z,
2096 5,
2097 Scaling::Unscaled,
2098 TOL,
2099 "H1 crossing",
2100 );
2101 }
2102
2103 #[test]
2104 fn hankel1_seq_int_crossing() {
2105 let z = Complex64::new(1.5, 0.5);
2106 check_seq_vs_single(
2107 hankel1_seq,
2108 |nu, z| hankel1(nu, z),
2109 -2.0,
2110 z,
2111 5,
2112 Scaling::Unscaled,
2113 TOL,
2114 "H1 int cross",
2115 );
2116 }
2117
2118 // ── H^(2) ──
2119
2120 #[test]
2121 fn hankel2_seq_all_negative_nonint() {
2122 let z = Complex64::new(1.5, 0.5);
2123 check_seq_vs_single(
2124 hankel2_seq,
2125 |nu, z| hankel2(nu, z),
2126 -3.7,
2127 z,
2128 3,
2129 Scaling::Unscaled,
2130 TOL,
2131 "H2 all neg",
2132 );
2133 }
2134
2135 #[test]
2136 fn hankel2_seq_crossing_nonint() {
2137 let z = Complex64::new(1.5, 0.5);
2138 check_seq_vs_single(
2139 hankel2_seq,
2140 |nu, z| hankel2(nu, z),
2141 -1.5,
2142 z,
2143 5,
2144 Scaling::Unscaled,
2145 TOL,
2146 "H2 crossing",
2147 );
2148 }
2149
2150 #[test]
2151 fn hankel2_seq_int_crossing() {
2152 let z = Complex64::new(1.5, 0.5);
2153 check_seq_vs_single(
2154 hankel2_seq,
2155 |nu, z| hankel2(nu, z),
2156 -2.0,
2157 z,
2158 5,
2159 Scaling::Unscaled,
2160 TOL,
2161 "H2 int cross",
2162 );
2163 }
2164
2165 // ── J ──
2166
2167 #[test]
2168 fn besselj_seq_all_negative_nonint() {
2169 let z = Complex64::new(1.5, 0.5);
2170 check_seq_vs_single(
2171 besselj_seq,
2172 |nu, z| besselj(nu, z),
2173 -3.7,
2174 z,
2175 3,
2176 Scaling::Unscaled,
2177 TOL,
2178 "J all neg",
2179 );
2180 }
2181
2182 #[test]
2183 fn besselj_seq_crossing_nonint() {
2184 let z = Complex64::new(1.5, 0.5);
2185 check_seq_vs_single(
2186 besselj_seq,
2187 |nu, z| besselj(nu, z),
2188 -1.5,
2189 z,
2190 5,
2191 Scaling::Unscaled,
2192 TOL,
2193 "J crossing",
2194 );
2195 }
2196
2197 #[test]
2198 fn besselj_seq_int_negative() {
2199 let z = Complex64::new(1.5, 0.5);
2200 check_seq_vs_single(
2201 besselj_seq,
2202 |nu, z| besselj(nu, z),
2203 -3.0,
2204 z,
2205 4,
2206 Scaling::Unscaled,
2207 TOL,
2208 "J int neg",
2209 );
2210 }
2211
2212 #[test]
2213 fn besselj_seq_int_crossing() {
2214 let z = Complex64::new(1.5, 0.5);
2215 check_seq_vs_single(
2216 besselj_seq,
2217 |nu, z| besselj(nu, z),
2218 -2.0,
2219 z,
2220 5,
2221 Scaling::Unscaled,
2222 TOL,
2223 "J int cross",
2224 );
2225 }
2226
2227 // ── Y ──
2228
2229 #[test]
2230 fn bessely_seq_all_negative_nonint() {
2231 let z = Complex64::new(1.5, 0.5);
2232 check_seq_vs_single(
2233 bessely_seq,
2234 |nu, z| bessely(nu, z),
2235 -3.7,
2236 z,
2237 3,
2238 Scaling::Unscaled,
2239 TOL,
2240 "Y all neg",
2241 );
2242 }
2243
2244 #[test]
2245 fn bessely_seq_crossing_nonint() {
2246 let z = Complex64::new(1.5, 0.5);
2247 check_seq_vs_single(
2248 bessely_seq,
2249 |nu, z| bessely(nu, z),
2250 -1.5,
2251 z,
2252 5,
2253 Scaling::Unscaled,
2254 TOL,
2255 "Y crossing",
2256 );
2257 }
2258
2259 #[test]
2260 fn bessely_seq_int_negative() {
2261 let z = Complex64::new(1.5, 0.5);
2262 check_seq_vs_single(
2263 bessely_seq,
2264 |nu, z| bessely(nu, z),
2265 -3.0,
2266 z,
2267 4,
2268 Scaling::Unscaled,
2269 TOL,
2270 "Y int neg",
2271 );
2272 }
2273
2274 #[test]
2275 fn bessely_seq_int_crossing() {
2276 let z = Complex64::new(1.5, 0.5);
2277 check_seq_vs_single(
2278 bessely_seq,
2279 |nu, z| bessely(nu, z),
2280 -2.0,
2281 z,
2282 5,
2283 Scaling::Unscaled,
2284 TOL,
2285 "Y int cross",
2286 );
2287 }
2288
2289 // ── I ──
2290
2291 #[test]
2292 fn besseli_seq_all_negative_nonint() {
2293 let z = Complex64::new(1.5, 0.5);
2294 check_seq_vs_single(
2295 besseli_seq,
2296 |nu, z| besseli(nu, z),
2297 -3.7,
2298 z,
2299 3,
2300 Scaling::Unscaled,
2301 TOL,
2302 "I all neg",
2303 );
2304 }
2305
2306 #[test]
2307 fn besseli_seq_crossing_nonint() {
2308 let z = Complex64::new(1.5, 0.5);
2309 check_seq_vs_single(
2310 besseli_seq,
2311 |nu, z| besseli(nu, z),
2312 -1.5,
2313 z,
2314 5,
2315 Scaling::Unscaled,
2316 TOL,
2317 "I crossing",
2318 );
2319 }
2320
2321 #[test]
2322 fn besseli_seq_int_negative() {
2323 let z = Complex64::new(1.5, 0.5);
2324 check_seq_vs_single(
2325 besseli_seq,
2326 |nu, z| besseli(nu, z),
2327 -3.0,
2328 z,
2329 4,
2330 Scaling::Unscaled,
2331 TOL,
2332 "I int neg",
2333 );
2334 }
2335
2336 #[test]
2337 fn besseli_seq_int_crossing() {
2338 let z = Complex64::new(1.5, 0.5);
2339 check_seq_vs_single(
2340 besseli_seq,
2341 |nu, z| besseli(nu, z),
2342 -2.0,
2343 z,
2344 5,
2345 Scaling::Unscaled,
2346 TOL,
2347 "I int cross",
2348 );
2349 }
2350
2351 // ── Scaled variants ──
2352
2353 #[test]
2354 fn besseli_seq_scaled_negative_nonint() {
2355 let z = Complex64::new(2.0, 1.0);
2356 check_seq_vs_single(
2357 besseli_seq,
2358 |nu, z| besseli_scaled(nu, z),
2359 -2.3,
2360 z,
2361 5,
2362 Scaling::Exponential,
2363 TOL,
2364 "I scaled neg",
2365 );
2366 }
2367
2368 #[test]
2369 fn besselj_seq_scaled_crossing() {
2370 let z = Complex64::new(2.0, 1.0);
2371 check_seq_vs_single(
2372 besselj_seq,
2373 |nu, z| besselj_scaled(nu, z),
2374 -1.5,
2375 z,
2376 4,
2377 Scaling::Exponential,
2378 TOL,
2379 "J scaled cross",
2380 );
2381 }
2382
2383 #[test]
2384 fn besselk_seq_scaled_negative() {
2385 let z = Complex64::new(2.0, 1.0);
2386 check_seq_vs_single(
2387 besselk_seq,
2388 |nu, z| besselk_scaled(nu, z),
2389 -2.5,
2390 z,
2391 4,
2392 Scaling::Exponential,
2393 TOL,
2394 "K scaled neg",
2395 );
2396 }
2397
2398 #[test]
2399 fn hankel1_seq_scaled_negative() {
2400 let z = Complex64::new(2.0, 1.0);
2401 check_seq_vs_single(
2402 hankel1_seq,
2403 |nu, z| hankel1_scaled(nu, z),
2404 -2.5,
2405 z,
2406 4,
2407 Scaling::Exponential,
2408 TOL,
2409 "H1 scaled neg",
2410 );
2411 }
2412
2413 #[test]
2414 fn bessely_seq_scaled_crossing() {
2415 let z = Complex64::new(2.0, 1.0);
2416 check_seq_vs_single(
2417 bessely_seq,
2418 |nu, z| bessely_scaled(nu, z),
2419 -1.5,
2420 z,
2421 4,
2422 Scaling::Exponential,
2423 TOL,
2424 "Y scaled cross",
2425 );
2426 }
2427
2428 // ── Edge: -0.0 ──
2429
2430 #[test]
2431 fn besselj_seq_neg_zero() {
2432 let z = Complex64::new(1.0, 0.0);
2433 check_seq_vs_single(
2434 besselj_seq,
2435 |nu, z| besselj(nu, z),
2436 -0.0,
2437 z,
2438 3,
2439 Scaling::Unscaled,
2440 TOL,
2441 "J neg zero",
2442 );
2443 }
2444}