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