bacon_sci/special/polynomial/
mod.rs

1use crate::polynomial::Polynomial;
2use crate::roots::newton_polynomial;
3use nalgebra::ComplexField;
4use num_traits::FromPrimitive;
5
6/// Get the nth legendre polynomial.
7///
8/// Gets the nth legendre polynomial over a specified field. This is
9/// done using the recurrence relation and is properly normalized.
10///
11/// # Errors
12/// Returns an error if `tol` is invalid.
13///
14/// # Panics
15/// Panics if a u8 or a u32 can not be converted into the generic type.
16///
17/// # Examples
18/// ```
19/// use bacon_sci::special::legendre;
20/// fn example() {
21///     let p_3 = legendre::<f64>(3, 1e-8).unwrap();
22///     assert_eq!(p_3.order(), 3);
23///     assert!(p_3.get_coefficient(0).abs() < 0.00001);
24///     assert!((p_3.get_coefficient(1) + 1.5).abs() < 0.00001);
25///     assert!(p_3.get_coefficient(2).abs() < 0.00001);
26///     assert!((p_3.get_coefficient(3) - 2.5).abs() < 0.00001);
27/// }
28///
29pub fn legendre<N: ComplexField + FromPrimitive + Copy>(
30    n: u32,
31    tol: N::RealField,
32) -> Result<Polynomial<N>, String>
33where
34    <N as ComplexField>::RealField: FromPrimitive + Copy,
35{
36    if n == 0 {
37        let mut poly = polynomial![N::one()];
38        poly.set_tolerance(tol)?;
39        return Ok(poly);
40    }
41    if n == 1 {
42        let mut poly = polynomial![N::one(), N::zero()];
43        poly.set_tolerance(tol)?;
44        return Ok(poly);
45    }
46
47    let mut p_0 = polynomial![N::one()];
48    p_0.set_tolerance(tol)?;
49    let mut p_1 = polynomial![N::one(), N::zero()];
50    p_1.set_tolerance(tol)?;
51
52    for i in 1..n {
53        // Get p_i+1 from p_i and p_i-1
54        let mut p_next = polynomial![N::from_u32(2 * i + 1).unwrap(), N::zero()] * &p_1;
55        p_next.set_tolerance(tol)?;
56        p_next -= &p_0 * N::from_u32(i).unwrap();
57        p_next /= N::from_u32(i + 1).unwrap();
58        p_0 = p_1;
59        p_1 = p_next;
60    }
61
62    Ok(p_1)
63}
64
65/// Get the zeros of the nth legendre polynomial.
66/// Calculate zeros to tolerance `tol`, have polynomials
67/// with tolerance `poly_tol`.
68///
69/// # Errors
70/// Returns an error if `tol` or `poly_tol` are invalid
71pub fn legendre_zeros<N: ComplexField + FromPrimitive + Copy>(
72    n: u32,
73    tol: N::RealField,
74    poly_tol: N::RealField,
75    n_max: usize,
76) -> Result<Vec<N>, String>
77where
78    <N as ComplexField>::RealField: FromPrimitive + Copy,
79{
80    if n == 0 {
81        return Ok(vec![]);
82    }
83    if n == 1 {
84        return Ok(vec![N::zero()]);
85    }
86
87    let poly: Polynomial<N> = legendre(n, poly_tol)?;
88    Ok(poly
89        .roots(tol, n_max)?
90        .iter()
91        .map(|c| {
92            if c.re.abs() < tol {
93                N::zero()
94            } else {
95                N::from_real(c.re)
96            }
97        })
98        .collect())
99}
100
101/// Get the nth hermite polynomial.
102///
103/// Gets the nth physicist's hermite polynomial over a specified field. This is
104/// done using the recurrance relation so the normalization is standard for the
105/// physicist's hermite polynomial.
106///
107/// # Errors
108/// Returns an error if `tol` is invalid.
109///
110/// # Panics
111/// Panics if a u8 or u32 can not be converted into the generic type.
112///
113/// # Examples
114/// ```
115/// use bacon_sci::special::hermite;
116/// fn example() {
117///     let h_3 = hermite::<f64>(3, 1e-8).unwrap();
118///     assert_eq!(h_3.order(), 3);
119///     assert!(h_3.get_coefficient(0).abs() < 0.0001);
120///     assert!((h_3.get_coefficient(1) - 12.0).abs() < 0.0001);
121///     assert!(h_3.get_coefficient(2).abs() < 0.0001);
122///     assert!((h_3.get_coefficient(3) - 8.0).abs() < 0.0001);
123/// }
124/// ```
125pub fn hermite<N: ComplexField + FromPrimitive + Copy>(
126    n: u32,
127    tol: N::RealField,
128) -> Result<Polynomial<N>, String>
129where
130    <N as ComplexField>::RealField: FromPrimitive + Copy,
131{
132    if n == 0 {
133        let mut poly = polynomial![N::one()];
134        poly.set_tolerance(tol)?;
135        return Ok(poly);
136    }
137    if n == 1 {
138        let mut poly = polynomial![N::from_u8(2).unwrap(), N::zero()];
139        poly.set_tolerance(tol)?;
140        return Ok(poly);
141    }
142
143    let mut h_0 = polynomial![N::one()];
144    h_0.set_tolerance(tol)?;
145    let mut h_1 = polynomial![N::from_u8(2).unwrap(), N::zero()];
146    h_1.set_tolerance(tol)?;
147    let x_2 = h_1.clone();
148
149    for i in 1..n {
150        let next = &x_2 * &h_1 - (&h_0 * N::from_u32(2 * i).unwrap());
151        h_0 = h_1;
152        h_1 = next;
153    }
154
155    Ok(h_1)
156}
157
158/// Get the zeros of the nth Hermite polynomial within tolerance `tol` with polynomial
159/// tolerance `poly_tol`
160///
161/// # Errors
162/// Returns an error if `poly_tol` or `tol` are invalid.
163///
164/// # Panics
165/// Panics if a u32 or f32 can not be converted into the generic type.
166pub fn hermite_zeros<N: ComplexField + FromPrimitive + Copy>(
167    n: u32,
168    tol: N::RealField,
169    poly_tol: N::RealField,
170    n_max: usize,
171) -> Result<Vec<N>, String>
172where
173    <N as ComplexField>::RealField: FromPrimitive + Copy,
174{
175    if n == 0 {
176        return Ok(vec![]);
177    }
178    if n == 1 {
179        return Ok(vec![N::zero()]);
180    }
181
182    let poly: Polynomial<N> = hermite(n, poly_tol)?;
183
184    // Get initial guesses of zeros with asymptotic formula
185    let mut zeros = Vec::with_capacity(n as usize);
186    let special = N::from_f32(3.3721 / 6.0.cbrt()).unwrap();
187    let third = N::from_f32(1.0 / 3.0).unwrap().real();
188    for i in 1..=n {
189        let sqrt = N::from_u32(2 * i).unwrap().sqrt();
190        zeros.push(sqrt - special * sqrt.powf(-third));
191    }
192
193    // Use newton's method and deflation to refine guesses
194    let mut deflator = poly.clone();
195    let mut zs = Vec::with_capacity(zeros.len());
196    for z in &zeros {
197        let zero = newton_polynomial(*z, &deflator, tol, n_max)?;
198        let divisor = polynomial![N::one(), -zero];
199        let (quotient, _) = deflator.divide(&divisor)?;
200        // Adjust for round off error
201        let zero = newton_polynomial(zero, &poly, tol, n_max)?;
202        zs.push(zero);
203        deflator = quotient;
204    }
205    Ok(zs)
206}
207
208fn factorial<N: ComplexField + FromPrimitive>(k: u32) -> N {
209    let mut acc = N::one();
210    for i in 2..=k {
211        acc *= N::from_u32(i).unwrap();
212    }
213    acc
214}
215
216fn choose<N: ComplexField + FromPrimitive>(n: u32, k: u32) -> N {
217    let mut acc = N::one();
218    for i in n - k + 1..=n {
219        acc *= N::from_u32(i).unwrap();
220    }
221    for i in 2..=k {
222        acc /= N::from_u32(i).unwrap();
223    }
224    acc
225}
226
227/// Get the nth (positive) laguerre polynomial.
228///
229/// Gets the nth (positive) laguerre polynomial over a specified field. This is
230/// done using the direct formula and is properly normalized.
231///
232/// # Errors
233/// Returns an error if `tol` is invalid.
234///
235/// # Examples
236/// ```
237/// use bacon_sci::special::laguerre;
238/// fn example() {
239///     let p_3 = laguerre::<f64>(3, 1e-8).unwrap();
240///     assert_eq!(p_3.order(), 3);
241///     assert!((p_3.get_coefficient(0) - 1.0).abs() < 0.00001);
242///     assert!((p_3.get_coefficient(1) + 3.0).abs() < 0.00001);
243///     assert!((p_3.get_coefficient(2) - 9.0/6.0).abs() < 0.00001);
244///     assert!((p_3.get_coefficient(3) + 1.0/6.0).abs() < 0.00001);
245/// }
246///
247pub fn laguerre<N: ComplexField + Copy + FromPrimitive>(
248    n: u32,
249    tol: N::RealField,
250) -> Result<Polynomial<N>, String>
251where
252    <N as ComplexField>::RealField: FromPrimitive + Copy,
253{
254    let mut coefficients = Vec::with_capacity(n as usize + 1);
255    for k in 0..=n {
256        coefficients.push(
257            choose::<N>(n, k) / factorial::<N>(k) * if k % 2 == 0 { N::one() } else { -N::one() },
258        );
259    }
260
261    let mut poly: Polynomial<N> = coefficients.iter().copied().collect();
262    poly.set_tolerance(tol)?;
263    Ok(poly)
264}
265
266/// Get the zeros of the nth Laguerre polynomial
267///
268/// # Errors
269/// Returns an error if `tol` or `poly_tol` are invalid
270pub fn laguerre_zeros<N: ComplexField + Copy + FromPrimitive>(
271    n: u32,
272    tol: N::RealField,
273    poly_tol: N::RealField,
274    n_max: usize,
275) -> Result<Vec<N>, String>
276where
277    <N as ComplexField>::RealField: FromPrimitive + Copy,
278{
279    if n == 0 {
280        return Ok(vec![]);
281    }
282    if n == 1 {
283        return Ok(vec![N::one()]);
284    }
285
286    let poly: Polynomial<N> = laguerre(n, poly_tol)?;
287
288    Ok(poly
289        .roots(tol, n_max)?
290        .iter()
291        .map(|c| {
292            if c.re.abs() < tol {
293                N::zero()
294            } else {
295                N::from_real(c.re)
296            }
297        })
298        .collect())
299}
300
301/// Get the nth chebyshev polynomial.
302///
303/// Gets the nth chebyshev polynomial over a specified field. This is
304/// done using the recursive formula and is properly normalized.
305///
306/// # Errors
307/// Returns an error on invalid tolerance
308///
309/// # Panics
310/// Panics if a u8 can not be transformed into the generic type.
311///
312/// # Examples
313/// ```
314/// use bacon_sci::special::chebyshev;
315/// fn example() {
316///     let t_3 = chebyshev::<f64>(3, 1e-8).unwrap();
317///     assert_eq!(t_3.order(), 3);
318///     assert!(t_3.get_coefficient(0).abs() < 0.00001);
319///     assert!((t_3.get_coefficient(1) + 3.0).abs() < 0.00001);
320///     assert!(t_3.get_coefficient(2).abs() < 0.00001);
321///     assert!((t_3.get_coefficient(3) - 4.0).abs() < 0.00001);
322/// }
323///
324pub fn chebyshev<N: ComplexField + FromPrimitive + Copy>(
325    n: u32,
326    tol: N::RealField,
327) -> Result<Polynomial<N>, String>
328where
329    <N as ComplexField>::RealField: FromPrimitive + Copy,
330{
331    if n == 0 {
332        let mut poly = polynomial![N::one()];
333        poly.set_tolerance(tol)?;
334        return Ok(poly);
335    }
336    if n == 1 {
337        let mut poly = polynomial![N::one(), N::zero()];
338        poly.set_tolerance(tol)?;
339        return Ok(poly);
340    }
341
342    let half = chebyshev(n / 2, tol)?;
343    if n % 2 == 0 {
344        Ok(&half * &half * N::from_u8(2).unwrap() - polynomial![N::one()])
345    } else {
346        let other_half = chebyshev(n / 2 + 1, tol)?;
347        Ok(&half * &other_half * N::from_u8(2).unwrap() - polynomial![N::one(), N::zero()])
348    }
349}
350
351/// Get the nth chebyshev polynomial of the second kind.
352///
353/// Gets the nth chebyshev polynomial of the second kind over a specified field. This is
354/// done using the recursive formula and is properly normalized.
355///
356/// # Errors
357/// Returns an error on invalid tolerance
358///
359/// # Panics
360/// Panics if a u8 can not be transformed into the generic type.
361///
362/// # Examples
363/// ```
364/// use bacon_sci::special::chebyshev_second;
365/// fn example() {
366///     let u_3 = chebyshev_second::<f64>(3, 1e-8).unwrap();
367///     assert_eq!(u_3.order(), 3);
368///     assert!(u_3.get_coefficient(0).abs() < 0.00001);
369///     assert!((u_3.get_coefficient(1) + 4.0).abs() < 0.00001);
370///     assert!(u_3.get_coefficient(2).abs() < 0.00001);
371///     assert!((u_3.get_coefficient(3) - 8.0).abs() < 0.00001);
372/// }
373///
374pub fn chebyshev_second<N: ComplexField + FromPrimitive + Copy>(
375    n: u32,
376    tol: N::RealField,
377) -> Result<Polynomial<N>, String>
378where
379    <N as ComplexField>::RealField: FromPrimitive + Copy,
380{
381    if n == 0 {
382        let mut poly = polynomial![N::one()];
383        poly.set_tolerance(tol)?;
384        return Ok(poly);
385    }
386    if n == 1 {
387        let mut poly = polynomial![N::from_u8(2).unwrap(), N::zero()];
388        poly.set_tolerance(tol)?;
389        return Ok(poly);
390    }
391
392    let mut t_0 = polynomial![N::one()];
393    t_0.set_tolerance(tol)?;
394    let mut t_1 = polynomial![N::from_u8(2).unwrap(), N::zero()];
395    t_1.set_tolerance(tol)?;
396    let double = t_1.clone();
397
398    for _ in 1..n {
399        let next = &double * &t_1 - &t_0;
400        t_0 = t_1;
401        t_1 = next;
402    }
403    Ok(t_1)
404}