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}