rsl_polynomials/
polynomial.rs

1//! Methods for evaluating a polynomial and its derivatives on a certain point.
2
3use num::Zero;
4
5use crate::{
6    PolyError, Result, solve,
7    utils::{check_if_correct_order, check_if_real_coefficients, convert_complex_to_real},
8};
9
10#[allow(rustdoc::broken_intra_doc_links)]
11/// Representation of a polynomial.
12///
13/// A polynomial of degree `n`, represented with a [`Vec`] of length `n+1` containing the coefficients
14/// `c[i]`:
15///
16/// P(x) = c[0] + c[1]x + c[2]x² + ... + c[n−1]xⁿ⁻¹ + c[n]xⁿ
17///
18/// [`Vec`]: std::vec::Vec
19#[derive(Clone)]
20pub struct Polynomial<T>
21where
22    T: std::fmt::Debug,
23{
24    /// The polynomial's coefficients, from constant to leading term.
25    pub coef: Vec<T>,
26}
27
28impl<T> Polynomial<T>
29where
30    T: num::complex::ComplexFloat + std::fmt::Debug,
31{
32    /// Creates a new Polynomial with no terms (zero polynomial).
33    pub fn new() -> Self {
34        Polynomial {
35            coef: vec![T::zero()],
36        }
37    }
38
39    /// Creates a new Polynomial from the given coefficients.
40    ///
41    /// ## Example
42    ///
43    /// ```
44    /// # use rsl_polynomials::{Polynomial, Result};
45    /// # fn main() -> Result<()> {
46    /// let poly = Polynomial::build(&[1.0, 4.0, 3.0])?; // 1+4x+3x²
47    /// # Ok(())
48    /// # }
49    /// ```
50    pub fn build(coef: &[T]) -> Result<Self> {
51        if coef.len().is_zero() {
52            return Ok(Polynomial::new());
53        }
54
55        match coef.iter().any(|x| x.is_nan() | x.is_infinite()) {
56            true => Err(PolyError::InvalidCoefficients),
57            false => Ok(Polynomial {
58                coef: coef.to_vec(),
59            }),
60        }
61    }
62
63    /// Trims the higher order terms with 0 coefficient.
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// # use rsl_polynomials::{Polynomial, Result};
69    /// # fn main() -> Result<()> {
70    /// // 0+x+0+2x³+0 −> x+2x³
71    /// let poly = Polynomial::build(&[0.0, 1.0, 0.0, 2.0, 0.0])?.to_trimmed();
72    ///
73    /// assert_eq!(poly.coef, &[0.0, 1.0, 0.0, 2.0]);
74    /// # Ok(())
75    /// # }
76    /// ```
77    pub fn to_trimmed(&self) -> Self {
78        // Leave [0.0] polynomial as is
79        if self.coef.len() == 1 {
80            return self.clone();
81        }
82
83        let mut iter = self.coef.iter().rev().copied().peekable();
84        let mut new_coeffs = self.coef.clone();
85
86        new_coeffs.reverse();
87        while iter.peek().is_some_and(|c| c.is_zero()) {
88            new_coeffs.remove(0);
89            iter.next();
90        }
91        new_coeffs.reverse();
92
93        Polynomial { coef: new_coeffs }
94    }
95
96    /// Converts a general polynomial to a [`monic`] polynomial:
97    /// ax³ + bx² + cx + d  −>  x³ + a'x² + b'x + c'
98    ///
99    /// ## Example
100    ///
101    /// ```
102    /// # use rsl_polynomials::{Polynomial, Result};
103    /// # fn main() -> Result<()> {
104    /// let poly = Polynomial::build(&[30.0, 6.0, 3.0])?.to_monic();
105    ///
106    /// assert_eq!(poly.coef, &[10.0, 2.0, 1.0]);
107    /// # Ok(())
108    /// # }
109    /// ```
110    ///
111    /// [`monic`]: https://en.wikipedia.org/wiki/Monic_polynomial
112    pub fn to_monic(&self) -> Self {
113        // Leave [0.0] polynomial as is
114        if self.coef.len() == 1 {
115            return self.clone();
116        }
117
118        let mut monic = self.to_trimmed();
119        let a = *monic.coef.last().unwrap();
120        monic.coef.iter_mut().for_each(|e| *e = *e / a);
121        monic
122    }
123
124    /// Converts a general cubic polynomial to a [`depressed cubic`] polynomial:
125    /// ax³ + bx³ + cx + d  −> t³ + pt + q,  where t = x − b/3a
126    ///
127    /// ## Example
128    ///
129    /// ```
130    /// # use rsl_polynomials::{Polynomial, Result};
131    /// # fn main() -> Result<()> {
132    /// let poly = Polynomial::build(&[30.0, 6.0, 3.0])?.to_depressed_cubic();
133    /// # Ok(())
134    /// # }
135    /// ```
136    ///
137    /// [`depressed cubic`]: https://en.wikipedia.org/wiki/Cubic_equation#Depressed_cubic
138    pub fn to_depressed_cubic(&self) -> Result<Polynomial<f64>> {
139        let poly = self.to_trimmed();
140        check_if_real_coefficients(&poly.coef)?;
141        check_if_correct_order(&poly.coef, 3)?;
142
143        let mut reals = Vec::<f64>::new();
144        for c in poly.coef.iter() {
145            reals.push(convert_complex_to_real(*c)?);
146        }
147
148        let a = reals[3];
149        let b = reals[2];
150        let c = reals[1];
151        let d = reals[0];
152
153        let p = (3.0 * a * c - b.powi(2)) / (3.0 * a.powi(2));
154        let q = (2.0 * b.powi(3) - 9.0 * a * b * c + 27.0 * a.powi(2) * d) / (27.0 * a.powi(3));
155
156        Polynomial::build(&[q, p, 0.0, 1.0])
157    }
158
159    /// Evaluates the polynomial for the value `x`.
160    ///
161    /// ## Example
162    ///
163    /// ```
164    /// # use rsl_polynomials::{Polynomial, Result};
165    /// # fn main() -> Result<()> {
166    /// let poly = Polynomial::build(&[1.0, 2.0, 3.0])?;
167    ///
168    /// assert_eq!(poly.eval(1.0), 6.0);
169    /// assert_eq!(poly.eval(-1.0), 2.0);
170    /// # Ok(())
171    /// # }
172    /// ```
173    #[doc(alias = "gsl_poly_eval")]
174    pub fn eval(&self, x: T) -> T {
175        // NOTE: This evaluates a₀+a₁x+a₂x²+...+aₙx² as if it were in the form
176        // a₀+x(a₁+x(a₂+ x(...))), therefore saving a lot of reduntant multiplications.
177
178        // NOTE: `gsl_poly_complex_eval()` and `gsl_complex_poly_complex_eval()` seem to do the
179        // same thing as `gsl_poly_eval()`, but perform the complex addition and multiplication
180        // manually since its slightly faster.
181
182        self.coef
183            .iter()
184            .rev()
185            .copied()
186            .reduce(|res, coef| coef + x * res)
187            .unwrap_or(T::zero())
188    }
189
190    /// Evaluates the polynomials first `n` derivatives (including the 0-th derivative, i.e. the
191    /// polynomial's value) for the value `x`.
192    ///
193    /// The result is a vector holding the calculated derivatives:
194    ///
195    /// [d⁰/dx⁰, d¹/dx¹, d²/dx², ..., dⁿ/dxⁿ]
196    ///
197    /// ## Example
198    ///
199    /// ```
200    /// # use rsl_polynomials::{Polynomial, Result};
201    /// # fn main() -> Result<()> {
202    /// let poly = Polynomial::build(&[1.0, 2.0, 3.0])?;
203    ///
204    /// assert_eq!(poly.eval_derivs(1.0, 4), &[6.0, 8.0, 6.0, 0.0]);
205    /// # Ok(())
206    /// # }
207    /// ```
208    #[doc(alias = "gsl_poly_eval_derivs")]
209    pub fn eval_derivs(&self, x: T, n: usize) -> Vec<T> {
210        let mut res: Vec<T> = vec![T::zero(); n];
211
212        let last_idx = self.coef.len() - 1;
213        let nmax = self.coef.len().min(res.len()) - 1;
214
215        // Partially fill res with the dominant term's coefficient
216        res.iter_mut()
217            .take(nmax + 1)
218            .for_each(|e| *e = *self.coef.last().unwrap());
219
220        for i in 0..last_idx {
221            let k = last_idx - i;
222            res[0] = x * res[0] + self.coef[k - 1];
223            let jmax = if nmax < k { nmax } else { k - 1 };
224            for j in 1..=jmax {
225                res[j] = x * res[j] + res[j - 1];
226            }
227        }
228
229        // Mutliply each term by the corresponding exponents
230        let mut f = T::one();
231        for (i, d) in res.iter_mut().enumerate().take(nmax + 1).skip(2) {
232            f = f * T::from(i).unwrap();
233            *d = *d * f;
234        }
235
236        res
237    }
238
239    /// Calculates the **real** roots af a quadratic equation `ax²+bx+c`.
240    ///
241    /// # Error
242    ///
243    /// Returns an error in 3 cases:
244    /// 1. the Polynomial is not of order 2
245    /// 2. one of the coefficients is not real
246    /// 3. the Polynomial is constant, i.e. a=b=0
247    ///
248    /// # Example
249    ///
250    /// ```
251    /// # use rsl_polynomials::{Polynomial, Result};
252    /// #
253    /// # fn main() -> Result<()> {
254    /// let poly = Polynomial::build(&[-20.0, 0.0, 5.0])?; // 5x²-20
255    /// let y = poly.solve_real_quadratic()?;
256    /// let expected = [2.0, -2.0];
257    ///
258    /// assert_eq!(y, expected);
259    /// # Ok(())
260    /// # }
261    /// ```
262    #[doc(alias = "gsl_poly_solve_quadratic")]
263    pub fn solve_real_quadratic(&self) -> Result<Vec<f64>> {
264        check_if_correct_order(&self.coef, 2)?;
265        check_if_real_coefficients(&self.coef)?;
266
267        let mut reals = Vec::<f64>::new();
268        for c in self.coef.iter() {
269            reals.push(convert_complex_to_real(*c)?);
270        }
271
272        solve::solve_real_quadratic(reals[2], reals[1], reals[0])
273    }
274
275    /// Calculates the **real** roots af a quadratic equation `ax³+bx²+cx+d`.
276    ///
277    /// The roots are returned in increasing order.
278    ///
279    /// ## Note
280    ///
281    /// Due to finite precision, some double roots may be missed, and considered to be a
282    /// pair of complex roots z = x ± i*EPSILON close to the real axis.
283    ///
284    /// # Error
285    ///
286    /// Returns an error in 3 cases:
287    /// 1. the Polynomial is not of order 3
288    /// 2. one of the coefficients is not real
289    /// 3. the Polynomial is constant, i.e. a=b=c=0
290    ///
291    /// # Example
292    ///
293    /// ```
294    /// # use rsl_polynomials::{Polynomial, Result};
295    /// #
296    /// # fn main() -> Result<()> {
297    /// let poly = Polynomial::build(&[-6.0, 11.0, -6.0, 1.0])?; // x³-6x²+11x-6
298    /// let y = poly.solve_real_cubic()?;
299    /// let expected = [1.0, 2.0, 3.0];
300    ///
301    /// assert_eq!(y, expected);
302    /// # Ok(())
303    /// # }
304    /// ```
305    #[doc(alias = "gsl_poly_solve_cubic")]
306    pub fn solve_real_cubic(&self) -> Result<Vec<f64>> {
307        check_if_correct_order(&self.coef, 3)?;
308        check_if_real_coefficients(&self.coef)?;
309
310        let monic = self.to_monic();
311
312        let mut reals = Vec::<f64>::new();
313        for c in monic.coef.iter() {
314            reals.push(convert_complex_to_real(*c)?);
315        }
316
317        solve::solve_real_cubic(reals[2], reals[1], reals[0])
318    }
319}
320
321impl<T> Default for Polynomial<T>
322where
323    T: num::complex::ComplexFloat + std::fmt::Debug,
324{
325    fn default() -> Self {
326        Self::new()
327    }
328}
329
330impl<T> std::fmt::Debug for Polynomial<T>
331where
332    T: std::fmt::Debug,
333{
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        f.debug_struct("Polynomial")
336            .field("Coefficients", &self.coef)
337            .finish()
338    }
339}