rust_poly/
poly.rs

1use std::fmt::Display;
2
3use itertools::Itertools;
4use num::{Complex, One, Zero};
5
6use crate::{
7    util::{
8        complex::{c_neg, complex_fmt, complex_sort_mut},
9        doc_macros::panic_absurd_size,
10    },
11    RealScalar,
12};
13
14mod base;
15mod calculus;
16mod conversions;
17mod impl_num;
18mod indexing;
19pub mod roots;
20mod special_funcs;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Poly<T: RealScalar>(pub(crate) Vec<Complex<T>>);
24
25impl<T: RealScalar> Poly<T> {
26    /// # Examples
27    /// ```
28    /// # use rust_poly::{poly, Poly};
29    /// let p = poly![1.0, 2.0, 3.0];
30    /// assert_eq!(p.shift_up(2), poly![0.0, 0.0, 1.0, 2.0, 3.0]);
31    /// ```
32    #[must_use]
33    pub fn shift_up(&self, n: usize) -> Self {
34        let mut v = vec![Complex::<T>::zero(); n];
35        v.extend_from_slice(self.as_slice());
36        Self::from_complex_vec(v)
37    }
38
39    /// # Examples
40    /// ```
41    /// # use rust_poly::{poly, Poly};
42    /// let p = poly![1.0, 2.0, 3.0, 4.0];
43    /// assert_eq!(p.shift_down(2), poly![3.0, 4.0]);
44    /// ```
45    #[must_use]
46    pub fn shift_down(&self, n: usize) -> Self {
47        Self::from_complex_slice(&self.as_slice()[n..])
48    }
49}
50
51impl<T: RealScalar> Poly<T> {
52    pub fn new(coeffs: &[Complex<T>]) -> Self {
53        Self(coeffs.to_owned()).normalize()
54    }
55
56    /// Complex constant as a polynomial of degree zero
57    #[inline(always)]
58    #[must_use]
59    pub fn constant(c: Complex<T>) -> Self {
60        Self::new(&[c])
61    }
62
63    /// Linear function as a polynomial.
64    ///
65    /// # Examples
66    /// ```
67    /// use rust_poly::Poly;
68    /// use num::Complex;
69    /// use num::{One, Zero};
70    ///
71    /// assert_eq!(Poly::line(Complex::one(), Complex::new(-1.0, 0.0)).eval_point(Complex::one()), Complex::zero());
72    /// ```
73    pub fn line(offset: Complex<T>, slope: Complex<T>) -> Self {
74        if slope.is_zero() {
75            return Self::new(&[offset]);
76        }
77        Self::new(&[offset, slope])
78    }
79
80    /// Line between two points with complex coordinates.
81    ///
82    /// Note that the points are determined by two complex numbers, so they are
83    /// in a four dimensional space. Leave the imaginary component as zero for lines
84    /// in a 2D plane.
85    ///
86    /// # Examples
87    /// ```
88    /// use rust_poly::Poly;
89    /// use num::Complex;
90    /// use num::{One, Zero};
91    ///
92    /// let p1 = (Complex::new(-1.0, 0.0), Complex::new(2.0, 0.0));
93    /// let p2 = (Complex::new(2.0, 0.0), Complex::new(-1.0, 0.0));
94    ///
95    /// assert_eq!(Poly::line_from_points(p1, p2).eval_point(Complex::one()), Complex::zero());
96    /// ```
97    pub fn line_from_points(p1: (Complex<T>, Complex<T>), p2: (Complex<T>, Complex<T>)) -> Self {
98        let slope = (p2.1 - p1.1.clone()) / (p2.0 - p1.0.clone());
99        let offset = p1.1.clone() - slope.clone() * p1.0;
100        Self::line(offset, slope)
101    }
102
103    /// Create a polynomial from a single term (coefficient + degree)
104    ///
105    /// # Examples
106    /// ```
107    /// use rust_poly::{poly, Poly};
108    /// use num::Complex;
109    /// use num::One;
110    ///
111    /// assert_eq!(Poly::term(Complex::one(), 3), poly![0.0, 0.0, 0.0, 1.0]);
112    /// ```
113    pub fn term(coeff: Complex<T>, degree: u32) -> Self {
114        Self::line(Complex::zero(), complex!(T::one())).pow(degree) * coeff
115    }
116
117    #[must_use]
118    pub fn len(&self) -> usize {
119        debug_assert!(self.is_normalized());
120        self.len_raw()
121    }
122
123    /// Return the degree as `usize`.
124    ///
125    /// Note that unlike [`Poly::degree`], this will saturate at 0 for zero
126    /// polynomials. As the degree of zero polynomials is undefined.
127    #[must_use]
128    pub fn degree_usize(&self) -> usize {
129        debug_assert!(self.is_normalized());
130        self.degree_raw()
131    }
132
133    /// The degree of a polynomial (the maximum exponent)
134    ///
135    /// Note that this will return `-1` for zero polynomials. The degree of
136    /// zero polynomials is undefined, but we use the `-1` convention adopted
137    /// by some authors.
138    ///
139    /// # Panics
140    #[doc = panic_absurd_size!()]
141    #[must_use]
142    pub fn degree(&self) -> i64 {
143        debug_assert!(self.is_normalized());
144        if self.is_zero() {
145            return -1;
146        }
147        self.degree_raw()
148            .try_into()
149            .expect("infallible except for absurd cases")
150    }
151
152    #[must_use]
153    pub fn is_empty(&self) -> bool {
154        self.len() == 0
155    }
156
157    /// Raises a polynomial to an integer power.
158    ///
159    /// # Caveats
160    /// We adopt the convention that $0^0=1$, even though some authors leave this
161    /// case undefined. We believe this to be more useful as it naturally arises
162    /// in integer exponentiation when defined as repeated multiplication (as we
163    /// implement it).
164    /// ```
165    /// use rust_poly::{poly, Poly};
166    /// use num::Complex;
167    ///
168    /// assert_eq!(poly![1.0, 2.0, 3.0].pow(2), poly![1.0, 4.0, 10.0, 12.0, 9.0]);
169    /// ```
170    #[must_use]
171    pub fn pow(self, pow: u32) -> Self {
172        self.pow_usize(pow as usize)
173    }
174
175    /// Same as [`Poly::pow`], but takes a `usize` exponent.
176    #[must_use]
177    pub fn pow_usize(self, pow: usize) -> Self {
178        // invariant: poly is normalized
179        debug_assert!(self.is_normalized());
180
181        if pow == 0 {
182            return Self::one();
183        }
184
185        if pow == 1 {
186            return self;
187        }
188
189        if self.is_zero() {
190            return self;
191        }
192
193        // TODO: divide and conquer with powers of 2
194        let mut res = self.clone();
195        for _ in 2..=pow {
196            res = res * self.clone();
197        }
198        res.normalize()
199    }
200
201    /// Compute the conjugate polynomial, that is a polynomial where every
202    /// coefficient is conjugated.
203    ///
204    /// To evaluate a conjugate polynomial, you must evaluate it at the conjugate
205    /// of the input, i.e. `poly.conj().eval(z.conj())`
206    #[must_use]
207    pub fn conj(&self) -> Self {
208        Self(self.0.iter().cloned().map(|z| z.conj()).collect_vec()).normalize()
209    }
210
211    /// Get the nth term of the polynomial as a new polynomial
212    ///
213    /// Will return None if out of bounds.
214    ///
215    /// # Examples
216    /// ```
217    /// use rust_poly::{poly, Poly};
218    /// use num::Complex;
219    /// use num::One;
220    ///
221    /// let p  = poly![1.0, 2.0, 3.0];
222    /// assert_eq!(p.get_term(1).unwrap(), poly![0.0, 2.0]);
223    /// ```
224    #[must_use]
225    pub fn get_term(&self, degree: u32) -> Option<Self> {
226        if degree as usize >= self.len_raw() {
227            return None;
228        }
229        Some(Self::term(self.as_slice()[degree as usize].clone(), degree))
230    }
231
232    /// Iterate over the terms of a polynomial
233    ///
234    /// ```
235    /// # use rust_poly::{poly, Poly};
236    ///
237    /// let p = poly![1.0, 2.0, 3.0];
238    /// assert_eq!(p, p.terms().sum::<Poly<_>>());
239    /// ```
240    ///
241    /// # Panics
242    /// On polynomials with a degree higher than `u32::MAX`
243    pub fn terms(&self) -> std::iter::Map<std::ops::Range<usize>, impl FnMut(usize) -> Self + '_> {
244        debug_assert!(self.is_normalized());
245        (0..self.len_raw()).map(|i| {
246            self.get_term(
247                i.try_into()
248                    .expect("degrees above u32::MAX are not supported"),
249            )
250            .expect("terms are within range len_raw, this should never fail")
251        })
252    }
253}
254
255impl<T: RealScalar + PartialOrd> Poly<T> {
256    /// Monic polynomial from its complex roots.
257    ///
258    /// # Examples
259    /// ```
260    /// use rust_poly::Poly;
261    /// use num::Complex;
262    /// use num::{Zero, One};
263    ///
264    /// let p = Poly::from_roots(&[Complex::new(-1.0, 0.0), Complex::zero(), Complex::one()]);
265    /// assert_eq!(p, Poly::new(&[Complex::zero(), Complex::new(-1.0, 0.0), Complex::zero(), Complex::one()]))
266    /// ```
267    #[must_use]
268    pub fn from_roots(roots: &[Complex<T>]) -> Self {
269        if roots.is_empty() {
270            return Self::one();
271        }
272
273        let mut roots = roots.to_owned();
274        complex_sort_mut(roots.as_mut_slice());
275
276        roots
277            .into_iter()
278            .map(|e| Self::line(c_neg(e), Complex::<T>::one()))
279            .fold(Self::one(), |acc, x| acc * x)
280            .normalize()
281    }
282
283    /// Compose two polynomials, returning a new polynomial.
284    ///
285    /// Substitute the given polynomial `x` into `self` and expand the
286    /// result into a new polynomial.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use rust_poly::{Poly, poly};
292    /// use num::{One, Complex};
293    ///
294    /// let f = poly![1.0, 2.0];
295    /// let g = Poly::one();
296    ///
297    /// assert_eq!(f.clone().compose(g), f);
298    #[must_use]
299    pub fn compose(self, x: Self) -> Self {
300        // invariant: polynomials are normalized
301        debug_assert!(self.is_normalized());
302        debug_assert!(x.is_normalized());
303
304        if self.is_one() {
305            return x;
306        }
307
308        if x.is_one() {
309            return self;
310        }
311
312        (0..self.len_raw())
313            .map(|i| Self::new(&[self.0[i].clone()]) * x.clone().pow_usize(i))
314            .sum()
315    }
316}
317
318impl<T: RealScalar> Poly<T> {
319    /// Evaluate the polynomial for each entry of a slice.
320    pub fn eval_multiple(&self, points: &[Complex<T>], out: &mut [Complex<T>]) {
321        debug_assert!(self.is_normalized());
322
323        // TODO: parallelize this loop
324        for (y, x) in out.iter_mut().zip(points) {
325            *y = self.eval(x.clone());
326        }
327    }
328
329    /// Evaluate the polynomial at a single value of `x`.
330    ///
331    /// ```
332    /// use rust_poly::Poly;
333    /// use num::Complex;
334    ///
335    /// let p = Poly::new(&[Complex::new(1.0, 0.0), Complex::new(2.0, 0.0), Complex::new(3.0, 0.0)]);
336    /// let x = Complex::new(1.0, 0.0);
337    /// assert_eq!(p.eval_point(x), Complex::new(6.0, 0.0));
338    /// ```
339    pub fn eval(&self, x: Complex<T>) -> Complex<T> {
340        // use Horner's method: https://en.wikipedia.org/wiki/Horner%27s_method
341        // in theory, Estrin's method is more parallelizable, but benchmarking
342        // against fast_polynomial crate shows no significant difference, this
343        // is close to optimal in terms of performance. You may get some small
344        // speedups by dividing large polynomials into 4 or 8 evaluations and
345        // computing them in parallel using SIMD, Rayon or GPU.
346        debug_assert!(self.is_normalized());
347        let mut eval = self.last();
348
349        // inlined len_raw() to ensure safety conditions are not broken by
350        // updating len_raw() implementation
351        let n = self.0.len();
352
353        for i in 1..n {
354            // SAFETY: index n - i - 1 is always in bounds
355            // PROOF:
356            // 1. the safe bounds for the index are [0, n - 1]
357            // 2. i is always in the range [1, n - 1]
358            // 3. the range of the index n - i - 1 is given by:
359            //    n - i - 1 => n - [1, n - 1] - 1
360            //             <=> [n - 1 - 1, n - 1 - (n - 1)]
361            //             <=> [n - 2, 0]
362            //             <=> [0, n - 2]   reversing bounds is equivalent
363            // 4. the range of the index [0, n - 2] is a subset of [0, n - 1],
364            //    therefore the index is in bounds. QED
365            let c = (unsafe { self.0.get_unchecked(n - i - 1) }).clone();
366            eval = eval * x.clone() + c;
367        }
368        eval
369    }
370}
371
372impl<T: RealScalar + PartialOrd> Poly<T> {
373    /// Translate along x-axis (or x-plane) and y-axis (or y-plane).
374    ///
375    /// Using complex coordinates means you'll effectively be translating in
376    /// 4D space.
377    #[must_use]
378    pub fn translate(mut self, x: Complex<T>, y: Complex<T>) -> Self {
379        self = self.compose(Self::from_complex_slice(&[c_neg(x), Complex::<T>::one()]));
380        self.0[0] += y;
381        self
382    }
383}
384
385impl<T: RealScalar> Poly<T> {
386    /// Returns true if every coefficient in the polynomial is smaller than the
387    /// tolerance (using complex norm).
388    ///
389    /// # Examples
390    /// ```
391    /// use rust_poly::{Poly, poly};
392    ///
393    /// assert!(poly![0.01, -0.01].almost_zero(&0.1));
394    /// ```
395    #[must_use]
396    pub fn almost_zero(&self, tolerance: &T) -> bool {
397        // invariant: polynomials are normalized
398        debug_assert!(self.is_normalized());
399
400        self.as_slice().iter().all(|c| c.norm_sqr() <= *tolerance)
401    }
402}
403
404impl<T: RealScalar + Display> Display for Poly<T> {
405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406        let mut iter = self.iter().enumerate();
407        if let Some((_, c)) = iter.next() {
408            write!(f, "{}", complex_fmt(c))?;
409        } else {
410            return Ok(());
411        }
412        for (i, c) in iter {
413            write!(f, " + {}*x^{}", complex_fmt(c), i)?;
414        }
415        Ok(())
416    }
417}
418
419#[cfg(test)]
420mod test {
421    #[test]
422    fn translate() {
423        let p = poly![1.0, 2.0, 3.0];
424        assert_eq!(
425            p.translate(complex!(1.0), complex!(2.0)),
426            poly![4.0, -4.0, 3.0]
427        );
428    }
429
430    #[test]
431    fn display() {
432        let p = poly![(2.0, 0.0), (4.5, 0.0), (5.0, 1.0), (6.0, 1.5), (7.0, 2.0)];
433        assert_eq!(
434            p.to_string(),
435            "2 + 4.5*x^1 + (5+i)*x^2 + (6+i1.5)*x^3 + (7+i2)*x^4".to_string()
436        );
437    }
438
439    #[test]
440    fn conj() {
441        let p = poly![(1.0, -1.0), (2.0, 2.0), (3.0, -3.0)];
442        let q = poly![(1.0, 1.0), (2.0, -2.0), (3.0, 3.0)];
443        assert_eq!(p.conj(), q);
444    }
445}