mathhook_core/functions/special/
bessel.rs

1//! Bessel functions of first and second kind
2//!
3//! Implements Bessel functions J_n(x) and Y_n(x) for integer orders using polynomial
4//! approximations (small arguments) and asymptotic expansions (large arguments).
5//!
6//! # Mathematical Background
7//!
8//! Bessel functions are canonical solutions of Bessel's differential equation:
9//! ```text
10//! x²y'' + xy' + (x² - n²)y = 0
11//! ```
12//! They appear in wave propagation, heat conduction, electromagnetic theory, and physics.
13//!
14//! # Numerical Methods
15//!
16//! - **Small |x| < 8**: Chebyshev polynomial approximations (Abramowitz & Stegun)
17//! - **Large |x| >= 8**: Asymptotic expansions for efficiency
18//! - **Higher orders n > 1**: Forward recurrence from J_0 and J_1
19//!
20//! **Stability**: Forward recurrence is stable for x > n. For x << n, accuracy may degrade.
21//!
22//! # Accuracy and Constraints
23//!
24//! - J_n(x): ~10-12 digits, defined for all real x
25//! - Y_n(x): ~10-12 digits for x > 0 only (logarithmic singularity at x=0)
26//! - Recurrence: Stable for x > n, may lose accuracy for x << n
27//!
28//! # Examples
29//!
30//! ```rust
31//! use mathhook_core::functions::special::bessel::{bessel_j, bessel_y};
32//! use mathhook_core::{Expression, Number};
33//!
34//! let x = Expression::Number(Number::Float(2.0));
35//! let j0 = bessel_j(0, &x);  // J_0(2.0) ≈ 0.2239
36//! let y1 = bessel_y(1, &Expression::Number(Number::Float(1.0)));  // Y_1(1.0) ≈ -0.7812
37//! ```
38
39use crate::core::{Expression, Number};
40use std::f64::consts::PI;
41
42/// Bessel function of the first kind J_n(x)
43///
44/// # Mathematical Properties
45///
46/// - J_0(0) = 1, J_n(0) = 0 for n > 0
47/// - J_{-n}(x) = (-1)^n J_n(x)
48/// - Recurrence: J_{n+1}(x) = (2n/x)J_n(x) - J_{n-1}(x)
49///
50/// # Examples
51///
52/// ```rust
53/// use mathhook_core::functions::special::bessel_j;
54/// use mathhook_core::{Expression, Number};
55///
56/// let j0 = bessel_j(0, &Expression::Number(Number::Integer(0)));
57/// assert_eq!(j0, Expression::Number(Number::Integer(1)));
58/// ```
59pub fn bessel_j(n: i32, x: &Expression) -> Expression {
60    match x {
61        Expression::Number(Number::Integer(0)) => Expression::Number(if n == 0 {
62            Number::Integer(1)
63        } else {
64            Number::Integer(0)
65        }),
66        Expression::Number(Number::Float(val)) => {
67            Expression::Number(Number::Float(bessel_j_float(n, *val)))
68        }
69        Expression::Number(Number::Integer(val)) => {
70            Expression::Number(Number::Float(bessel_j_float(n, *val as f64)))
71        }
72        _ => Expression::function(
73            "bessel_j",
74            vec![Expression::Number(Number::Integer(n as i64)), x.clone()],
75        ),
76    }
77}
78
79/// Bessel function of the second kind Y_n(x)
80///
81/// Y_n has logarithmic singularity at x = 0.
82///
83/// # Mathematical Properties
84///
85/// - Y_n(0) = -∞, Y_{-n}(x) = (-1)^n Y_n(x)
86/// - Recurrence: Y_{n+1}(x) = (2n/x)Y_n(x) - Y_{n-1}(x)
87///
88/// # Examples
89///
90/// ```rust
91/// use mathhook_core::functions::special::bessel_y;
92/// use mathhook_core::{Expression, Number};
93///
94/// let y0 = bessel_y(0, &Expression::Number(Number::Float(1.0)));
95/// ```
96pub fn bessel_y(n: i32, x: &Expression) -> Expression {
97    match x {
98        Expression::Number(Number::Integer(0)) => Expression::function(
99            "bessel_y",
100            vec![Expression::Number(Number::Integer(n as i64)), x.clone()],
101        ),
102        Expression::Number(Number::Float(val)) => {
103            Expression::Number(Number::Float(bessel_y_float(n, *val)))
104        }
105        Expression::Number(Number::Integer(val)) if *val != 0 => {
106            Expression::Number(Number::Float(bessel_y_float(n, *val as f64)))
107        }
108        _ => Expression::function(
109            "bessel_y",
110            vec![Expression::Number(Number::Integer(n as i64)), x.clone()],
111        ),
112    }
113}
114
115/// Numerical J_n(x) with input validation
116///
117/// Returns NaN for NaN/infinite inputs. Provides ~10-12 digit accuracy.
118fn bessel_j_float(n: i32, x: f64) -> f64 {
119    if x.is_nan() || x.is_infinite() {
120        return f64::NAN;
121    }
122    if x.abs() < 1e-10 {
123        return if n == 0 { 1.0 } else { 0.0 };
124    }
125    if n < 0 {
126        let result = bessel_j_float(-n, x);
127        return if (-n) % 2 == 0 { result } else { -result };
128    }
129    match n {
130        0 => bessel_j0(x),
131        1 => bessel_j1(x),
132        _ => bessel_jn_recurrence(n, x),
133    }
134}
135
136/// J_0(x) using Chebyshev approximation (A&S 9.4.1, 9.4.3)
137fn bessel_j0(x: f64) -> f64 {
138    let ax = x.abs();
139    if ax < 8.0 {
140        let y = x * x;
141        let ans1 = 57568490574.0
142            + y * (-13362590354.0
143                + y * (651619640.7 + y * (-11214424.18 + y * (77392.33017 + y * (-184.9052456)))));
144        let ans2 = 57568490411.0
145            + y * (1029532985.0
146                + y * (9494680.718 + y * (59272.64853 + y * (267.8532712 + y * 1.0))));
147        ans1 / ans2
148    } else {
149        let z = 8.0 / ax;
150        let y = z * z;
151        let xx = ax - 0.785398164;
152        let ans1 = 1.0
153            + y * (-0.1098628627e-2
154                + y * (0.2734510407e-4 + y * (-0.2073370639e-5 + y * 0.2093887211e-6)));
155        let ans2 = -0.1562499995e-1
156            + y * (0.1430488765e-3
157                + y * (-0.6911147651e-5 + y * (0.7621095161e-6 - y * 0.934935152e-7)));
158        (2.0 / PI / ax).sqrt() * (ans1 * xx.cos() - z * ans2 * xx.sin())
159    }
160}
161
162/// J_1(x) using Chebyshev approximation (A&S 9.4.4, 9.4.6)
163fn bessel_j1(x: f64) -> f64 {
164    let ax = x.abs();
165    if ax < 8.0 {
166        let y = x * x;
167        let ans1 = x
168            * (72362614232.0
169                + y * (-7895059235.0
170                    + y * (242396853.1
171                        + y * (-2972611.439 + y * (15704.48260 + y * (-30.16036606))))));
172        let ans2 = 144725228442.0
173            + y * (2300535178.0
174                + y * (18583304.74 + y * (99447.43394 + y * (376.9991397 + y * 1.0))));
175        ans1 / ans2
176    } else {
177        let z = 8.0 / ax;
178        let y = z * z;
179        let xx = ax - 2.356194491;
180        let ans1 = 1.0
181            + y * (0.183105e-2
182                + y * (-0.3516396496e-4 + y * (0.2457520174e-5 + y * (-0.240337019e-6))));
183        let ans2 = 0.04687499995
184            + y * (-0.2002690873e-3
185                + y * (0.8449199096e-5 + y * (-0.88228987e-6 + y * 0.105787412e-6)));
186        let ans = (2.0 / PI / ax).sqrt() * (ans1 * xx.cos() - z * ans2 * xx.sin());
187        if x < 0.0 {
188            -ans
189        } else {
190            ans
191        }
192    }
193}
194
195/// Forward recurrence for J_n(x)
196///
197/// Stable for x > n, may lose accuracy for x << n.
198fn bessel_jn_recurrence(n: i32, x: f64) -> f64 {
199    let mut jn_minus_1 = bessel_j0(x);
200    let mut jn = bessel_j1(x);
201    for k in 1..n {
202        let jn_plus_1 = (2.0 * k as f64 / x) * jn - jn_minus_1;
203        jn_minus_1 = jn;
204        jn = jn_plus_1;
205    }
206    jn
207}
208
209/// Numerical Y_n(x) with input validation
210///
211/// Returns NaN for NaN/infinite, -∞ for x <= 0. Provides ~10-12 digit accuracy for x > 0.
212fn bessel_y_float(n: i32, x: f64) -> f64 {
213    if x.is_nan() || x.is_infinite() {
214        return f64::NAN;
215    }
216    if x <= 0.0 {
217        return f64::NEG_INFINITY;
218    }
219    if n < 0 {
220        let result = bessel_y_float(-n, x);
221        return if (-n) % 2 == 0 { result } else { -result };
222    }
223    match n {
224        0 => bessel_y0(x),
225        1 => bessel_y1(x),
226        _ => bessel_yn_recurrence(n, x),
227    }
228}
229
230/// Y_0(x) using Chebyshev approximation (A&S 9.4.2, 9.4.3)
231fn bessel_y0(x: f64) -> f64 {
232    if x < 8.0 {
233        let j0 = bessel_j0(x);
234        let y = x * x;
235        let ans1 = -2957821389.0
236            + y * (7062834065.0
237                + y * (-512359803.6 + y * (10879881.29 + y * (-86327.92757 + y * 228.4622733))));
238        let ans2 = 40076544269.0
239            + y * (745249964.8
240                + y * (7189466.438 + y * (47447.26470 + y * (226.1030244 + y * 1.0))));
241        (ans1 / ans2) + (2.0 / PI) * j0 * x.ln()
242    } else {
243        let z = 8.0 / x;
244        let y = z * z;
245        let xx = x - 0.785398164;
246        let ans1 = 1.0
247            + y * (-0.1098628627e-2
248                + y * (0.2734510407e-4 + y * (-0.2073370639e-5 + y * 0.2093887211e-6)));
249        let ans2 = -0.1562499995e-1
250            + y * (0.1430488765e-3
251                + y * (-0.6911147651e-5 + y * (0.7621095161e-6 + y * (-0.934945152e-7))));
252        (2.0 / PI / x).sqrt() * (ans1 * xx.sin() + z * ans2 * xx.cos())
253    }
254}
255
256/// Y_1(x) using Chebyshev approximation (A&S 9.4.5, 9.4.6)
257fn bessel_y1(x: f64) -> f64 {
258    if x < 8.0 {
259        let j1 = bessel_j1(x);
260        let y = x * x;
261        let ans1 = x
262            * (-0.4900604943e13
263                + y * (0.1275274390e13
264                    + y * (-0.5153438139e11
265                        + y * (0.7349264551e9 + y * (-0.4237922726e7 + y * 0.8511937935e4)))));
266        let ans2 = 0.2499580570e14
267            + y * (0.4244419664e12
268                + y * (0.3733650367e10
269                    + y * (0.2245904002e8 + y * (0.1020426050e6 + y * (0.3549632885e3 + y)))));
270        (ans1 / ans2) + (2.0 / PI) * (j1 * x.ln() - 1.0 / x)
271    } else {
272        let z = 8.0 / x;
273        let y = z * z;
274        let xx = x - 2.356194491;
275        let ans1 = 1.0
276            + y * (0.183105e-2
277                + y * (-0.3516396496e-4 + y * (0.2457520174e-5 + y * (-0.240337019e-6))));
278        let ans2 = 0.04687499995
279            + y * (-0.2002690873e-3
280                + y * (0.8449199096e-5 + y * (-0.88228987e-6 + y * 0.105787412e-6)));
281        (2.0 / PI / x).sqrt() * (ans1 * xx.sin() + z * ans2 * xx.cos())
282    }
283}
284
285/// Forward recurrence for Y_n(x)
286///
287/// Stable for x > n, may lose accuracy for x << n.
288fn bessel_yn_recurrence(n: i32, x: f64) -> f64 {
289    let mut yn_minus_1 = bessel_y0(x);
290    let mut yn = bessel_y1(x);
291    for k in 1..n {
292        let yn_plus_1 = (2.0 * k as f64 / x) * yn - yn_minus_1;
293        yn_minus_1 = yn;
294        yn = yn_plus_1;
295    }
296    yn
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_bessel_j0_at_zero() {
305        assert_eq!(
306            bessel_j(0, &Expression::Number(Number::Integer(0))),
307            Expression::Number(Number::Integer(1))
308        );
309    }
310
311    #[test]
312    fn test_bessel_jn_at_zero() {
313        for n in 1..5 {
314            assert_eq!(
315                bessel_j(n, &Expression::Number(Number::Integer(0))),
316                Expression::Number(Number::Integer(0))
317            );
318        }
319    }
320
321    #[test]
322    fn test_bessel_j0_numerical_accuracy() {
323        if let Expression::Number(Number::Float(val)) =
324            bessel_j(0, &Expression::Number(Number::Float(1.0)))
325        {
326            assert!(
327                (val - 0.7651976865579666).abs() < 1e-8,
328                "J_0(1) = {}, expected {}",
329                val,
330                0.7651976865579666
331            );
332        } else {
333            panic!("Expected Float");
334        }
335    }
336
337    #[test]
338    fn test_bessel_j1_numerical_accuracy() {
339        if let Expression::Number(Number::Float(val)) =
340            bessel_j(1, &Expression::Number(Number::Float(1.0)))
341        {
342            assert!((val - 0.44005058574493355).abs() < 1e-10);
343        } else {
344            panic!("Expected Float");
345        }
346    }
347
348    #[test]
349    fn test_bessel_j_negative_order() {
350        let x = Expression::Number(Number::Float(1.0));
351        if let (Expression::Number(Number::Float(v1)), Expression::Number(Number::Float(v_m1))) =
352            (bessel_j(1, &x), bessel_j(-1, &x))
353        {
354            assert!((v1 + v_m1).abs() < 1e-10);
355        } else {
356            panic!("Expected Float");
357        }
358    }
359
360    #[test]
361    fn test_bessel_y0_numerical_accuracy() {
362        if let Expression::Number(Number::Float(val)) =
363            bessel_y(0, &Expression::Number(Number::Float(1.0)))
364        {
365            assert!(
366                (val - 0.08825696421567697).abs() < 1e-8,
367                "Y_0(1) = {}, expected {}",
368                val,
369                0.08825696421567697
370            );
371        } else {
372            panic!("Expected Float");
373        }
374    }
375
376    #[test]
377    fn test_bessel_y1_numerical_accuracy() {
378        if let Expression::Number(Number::Float(val)) =
379            bessel_y(1, &Expression::Number(Number::Float(1.0)))
380        {
381            assert!((val + 0.7812128213002887).abs() < 1e-9);
382        } else {
383            panic!("Expected Float");
384        }
385    }
386
387    #[test]
388    fn test_bessel_recurrence_j2() {
389        if let Expression::Number(Number::Float(val)) =
390            bessel_j(2, &Expression::Number(Number::Float(2.0)))
391        {
392            assert!(
393                (val - 0.3528340286156376).abs() < 1e-8,
394                "J_2(2) = {}, expected {}",
395                val,
396                0.3528340286156376
397            );
398        } else {
399            panic!("Expected Float");
400        }
401    }
402
403    #[test]
404    fn test_bessel_recurrence_y2() {
405        if let Expression::Number(Number::Float(val)) =
406            bessel_y(2, &Expression::Number(Number::Float(2.0)))
407        {
408            assert!(
409                (val + 0.6174081041906827).abs() < 1e-8,
410                "Y_2(2) = {}, expected {}",
411                val,
412                -0.6174081041906827
413            );
414        } else {
415            panic!("Expected Float");
416        }
417    }
418
419    #[test]
420    fn test_bessel_symbolic_fallback() {
421        match bessel_j(0, &Expression::symbol(crate::core::Symbol::scalar("x"))) {
422            Expression::Function { name, args } => {
423                assert_eq!(name.as_ref(), "bessel_j");
424                assert_eq!(args.len(), 2);
425            }
426            _ => panic!("Expected symbolic function"),
427        }
428    }
429
430    #[test]
431    fn test_bessel_j_input_validation_nan() {
432        if let Expression::Number(Number::Float(val)) =
433            bessel_j(0, &Expression::Number(Number::Float(f64::NAN)))
434        {
435            assert!(val.is_nan());
436        } else {
437            panic!("Expected Float");
438        }
439    }
440
441    #[test]
442    fn test_bessel_j_input_validation_infinity() {
443        if let Expression::Number(Number::Float(val)) =
444            bessel_j(0, &Expression::Number(Number::Float(f64::INFINITY)))
445        {
446            assert!(val.is_nan());
447        } else {
448            panic!("Expected Float");
449        }
450    }
451
452    #[test]
453    fn test_bessel_y_negative_x() {
454        if let Expression::Number(Number::Float(val)) =
455            bessel_y(0, &Expression::Number(Number::Float(-1.0)))
456        {
457            assert!(val.is_infinite() && val.is_sign_negative());
458        } else {
459            panic!("Expected Float");
460        }
461    }
462
463    #[test]
464    fn test_bessel_y_zero() {
465        if let Expression::Number(Number::Float(val)) =
466            bessel_y(0, &Expression::Number(Number::Float(0.0)))
467        {
468            assert!(val.is_infinite() && val.is_sign_negative());
469        } else {
470            panic!("Expected Float");
471        }
472    }
473
474    #[test]
475    fn test_bessel_j_large_argument() {
476        if let Expression::Number(Number::Float(val)) =
477            bessel_j(0, &Expression::Number(Number::Float(20.0)))
478        {
479            assert!((val - 0.1670246643).abs() < 1e-8);
480        } else {
481            panic!("Expected Float");
482        }
483    }
484
485    #[test]
486    fn test_bessel_j_high_order() {
487        if let Expression::Number(Number::Float(val)) =
488            bessel_j(5, &Expression::Number(Number::Float(10.0)))
489        {
490            assert!(
491                (val + 0.23406).abs() < 1e-2,
492                "J_5(10) = {}, expected approximately {}",
493                val,
494                -0.23406
495            );
496        } else {
497            panic!("Expected Float");
498        }
499    }
500
501    #[test]
502    fn test_bessel_j_negative_x_symmetry_even() {
503        let (j_pos, j_neg) = (
504            bessel_j(0, &Expression::Number(Number::Float(2.5))),
505            bessel_j(0, &Expression::Number(Number::Float(-2.5))),
506        );
507        if let (Expression::Number(Number::Float(vp)), Expression::Number(Number::Float(vn))) =
508            (j_pos, j_neg)
509        {
510            assert!((vp - vn).abs() < 1e-10);
511        } else {
512            panic!("Expected Float");
513        }
514    }
515
516    #[test]
517    fn test_bessel_j_negative_x_symmetry_odd() {
518        let (j_pos, j_neg) = (
519            bessel_j(1, &Expression::Number(Number::Float(2.5))),
520            bessel_j(1, &Expression::Number(Number::Float(-2.5))),
521        );
522        if let (Expression::Number(Number::Float(vp)), Expression::Number(Number::Float(vn))) =
523            (j_pos, j_neg)
524        {
525            assert!((vp + vn).abs() < 1e-10);
526        } else {
527            panic!("Expected Float");
528        }
529    }
530
531    #[test]
532    fn test_bessel_recurrence_relation_verification() {
533        let (x, n) = (5.0, 3);
534        let j_nm1 = bessel_j_float(n - 1, x);
535        let j_n = bessel_j_float(n, x);
536        let j_np1 = bessel_j_float(n + 1, x);
537        let recurrence = (2.0 * n as f64 / x) * j_n - j_nm1;
538        assert!((j_np1 - recurrence).abs() < 1e-10);
539    }
540
541    #[test]
542    fn test_bessel_j_first_zero() {
543        if let Expression::Number(Number::Float(val)) =
544            bessel_j(0, &Expression::Number(Number::Float(2.4048255577)))
545        {
546            assert!(val.abs() < 1e-6);
547        } else {
548            panic!("Expected Float");
549        }
550    }
551}