rust_poly/poly/
special_funcs.rs

1use crate::{
2    util::{
3        casting::{usize_to_f64, usize_to_i32, usize_to_u32},
4        luts::factorial_lut,
5    },
6    Poly, RealScalar,
7};
8use num::{BigUint, FromPrimitive};
9
10impl<T: RealScalar + FromPrimitive> Poly<T> {
11    #[deprecated(note = "use cheby1 instead")]
12    #[inline]
13    #[must_use]
14    pub fn cheby(n: usize) -> Self {
15        Self::cheby1(n)
16    }
17
18    /// Get the nth [Chebyshev polynomial](https://en.wikipedia.org/wiki/Chebyshev_polynomials)
19    /// of the first kind.
20    ///
21    /// ```
22    /// use rust_poly::{poly, Poly};
23    ///
24    /// assert_eq!(Poly::cheby(2), poly![-1.0, 0.0, 2.0]);
25    /// assert_eq!(Poly::cheby(3), poly![0.0, -3.0, 0.0, 4.0]);
26    /// assert_eq!(Poly::cheby(4), poly![1.0, 0.0, -8.0, 0.0, 8.0])
27    /// ```
28    // TODO: technically it can panic in some extreme cases, would need to
29    //       do some boundary testing to write a proper doc comment
30    #[allow(clippy::missing_panics_doc)]
31    #[must_use]
32    pub fn cheby1(n: usize) -> Self {
33        // TODO: make the first 32-ish explicit for performance
34        match n {
35            0 => poly![T::one()],
36            1 => poly![T::zero(), T::one()],
37            2 => poly![
38                T::zero() - T::one(),
39                T::zero(),
40                T::from_u8(2).expect("overflow")
41            ],
42            3 => poly![
43                T::zero(),
44                T::zero() - T::from_u8(3).expect("overflow"),
45                T::zero(),
46                T::from_u8(4).expect("overflow")
47            ],
48            4 => poly![
49                T::one(),
50                T::zero(),
51                T::zero() - T::from_u8(8).expect("overflow"),
52                T::zero(),
53                T::from_u8(8).expect("overflow")
54            ],
55            _ => {
56                poly![T::zero(), T::from_u8(2).expect("overflow")] * Self::cheby1(n - 1)
57                    - Self::cheby1(n - 2)
58            }
59        }
60    }
61
62    /// Get the nth [Bessel polynomial](https://en.wikipedia.org/wiki/Bessel_polynomials)
63    #[must_use]
64    pub fn bessel(n: usize) -> Option<Self> {
65        let mut poly = poly![];
66        for k in 0..=n {
67            let c = T::from_f64(coeff(n, k))?;
68            let term = Self::term(complex!(c), usize_to_u32(k));
69            poly = poly + term;
70        }
71        Some(poly)
72    }
73
74    #[must_use]
75    pub fn reverse_bessel(n: usize) -> Option<Self> {
76        let p = Self::bessel(n)?;
77        let v: Vec<_> = p.iter().cloned().rev().collect();
78        Some(Self::from_complex_vec(v))
79    }
80}
81
82impl<T: RealScalar> Poly<T> {
83    // TODO: technically it can panic in some extreme cases, would need to
84    //       do some boundary testing to write a proper doc comment
85    #[allow(clippy::missing_panics_doc)]
86    #[must_use]
87    pub fn legendre(n: usize) -> Self {
88        match n {
89            0 => return poly![T::one()],
90            1 => return poly![T::zero(), T::one()],
91            _ => {}
92        }
93
94        // this is the memoized form of the recursive recurrence relation definition
95        let mut memo = Vec::with_capacity(n - 1);
96        memo.push(poly![T::one()]);
97        memo.push(poly![T::zero(), T::one()]);
98
99        for i in 2..=n {
100            let p1 = &memo[i - 1];
101            let p2 = &memo[i - 2];
102            let ns = T::from_usize(i).expect("overflow");
103            memo.push(
104                poly![
105                    T::zero(),
106                    T::from_usize(2 * i - 1).expect("overflow") / ns.clone()
107                ] * p1
108                    + poly![(T::one() - ns.clone()) / ns] * p2,
109            );
110        }
111        memo.last().expect("infallible").clone()
112    }
113}
114
115fn factorial(n: usize) -> BigUint {
116    if n <= 128 {
117        return factorial_lut(n);
118    }
119    BigUint::from(n) * factorial(n - 1)
120}
121
122#[allow(clippy::cast_precision_loss)]
123fn biguint_to_f64(x: &BigUint) -> f64 {
124    let mut x_f: f64 = 0.0;
125    for (i, d) in x.to_u64_digits().iter().enumerate() {
126        x_f += (*d as f64) * 2.0f64.powi(usize_to_i32(i) * 64);
127    }
128    x_f
129}
130
131/// The coefficient for the k-th term of the n-th bessel polynomial
132pub fn coeff(n: usize, k: usize) -> f64 {
133    // NOTE: in theory f64 can do factorials up to 170!, but if we do it with
134    //       BigUint, as we divide by factorial(n - k), we get a smaller number
135    //       out, so this way we can squeeze a few more terms before f64
136    //       goes to infinity than if we computed the factorials with f64 directly
137    let aux_a = factorial(n + k) / factorial(n - k) / factorial(k);
138    let aux_b = 1.0 / (usize_to_f64(k)).exp2();
139    biguint_to_f64(&aux_a) * aux_b
140}
141
142#[cfg(test)]
143#[allow(clippy::pedantic)]
144mod test {
145    use num::{BigUint, Num};
146
147    use crate::{poly::special_funcs::biguint_to_f64, Poly, Poly64};
148
149    use super::factorial;
150
151    #[test]
152    fn factorial_129() {
153        let x = factorial(129);
154        let x_expected = BigUint::from_str_radix("49745042224772874403902341504126809639656611137138843145968864022652168932196355119328515747917449637889876686464600208839390308261862352651828829226610077151044469167497022952331930501120000000000000000000000000000000", 10).unwrap();
155        assert_eq!(x, x_expected);
156    }
157
158    #[test]
159    fn test_biguint_to_f64() {
160        let i = 1u128 << 90;
161        let x = BigUint::from(i);
162        assert_eq!(i as f64, biguint_to_f64(&x));
163    }
164
165    #[test]
166    #[cfg_attr(miri, ignore)] // takes way too long on MIRI
167    fn bessel_big() {
168        // largest computable bessel polynomial
169        let _ = Poly64::bessel(134).unwrap();
170    }
171
172    #[test]
173    #[cfg_attr(miri, ignore)] // takes way too long on MIRI
174    fn reverse_bessel_big() {
175        // largest computable reverse bessel
176        let _ = Poly64::reverse_bessel(134).unwrap();
177    }
178
179    #[test]
180    fn legendre() {
181        let p = Poly::<f32>::legendre(3);
182        assert_eq!(p, poly![0.0, -1.5, 0.0, 2.5]);
183    }
184}