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}