mathhook_core/core/polynomial/
poly.rs

1//! Generic univariate polynomial over any ring
2//!
3//! `Poly<T>` stores coefficients of type T for maximum performance and flexibility.
4//! This bypasses the Expression tree for 20-100x speedup on polynomial operations.
5//!
6//! # Type Aliases
7//!
8//! ```rust
9//! use mathhook_core::core::polynomial::poly::{Poly, IntPoly, RationalPoly};
10//! use num_rational::Ratio;
11//!
12//! // IntPoly is an alias for Poly<i64>
13//! let p: IntPoly = IntPoly::from_coeffs(vec![1, 2, 3]);
14//! let q: Poly<i64> = Poly::from_coeffs(vec![1, 2, 3]);
15//! assert_eq!(p, q);
16//!
17//! // RationalPoly is an alias for Poly<Ratio<i64>>
18//! let r: RationalPoly = RationalPoly::from_coeffs(vec![
19//!     Ratio::new(1, 2),
20//!     Ratio::new(3, 4),
21//! ]);
22//! ```
23
24mod arithmetic;
25mod conversion;
26mod display;
27mod division;
28
29use super::traits::Ring;
30use num_rational::Ratio;
31
32/// Generic univariate polynomial with coefficients of type T
33///
34/// Coefficients are stored in ascending order: ```coeffs[i]``` is the coefficient of x^i.
35/// The zero polynomial has an empty coefficient vector.
36///
37/// # Examples
38///
39/// ```rust
40/// use mathhook_core::core::polynomial::poly::IntPoly;
41///
42/// let p = IntPoly::from_coeffs(vec![1, 2, 3]);  // 1 + 2x + 3x²
43/// assert_eq!(p.degree(), Some(2));
44/// assert_eq!(p.leading_coeff(), 3);
45/// ```
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Poly<T: Ring> {
48    coeffs: Vec<T>,
49}
50
51/// Type alias for integer polynomials (backward compatibility)
52pub type IntPoly = Poly<i64>;
53
54/// Type alias for rational coefficient polynomials
55pub type RationalPoly = Poly<Ratio<i64>>;
56
57impl<T: Ring> Poly<T> {
58    /// Create polynomial from coefficients (ascending order)
59    ///
60    /// # Arguments
61    /// * `coeffs` - Coefficients where ``coeffs[i]`` is coefficient of x^i
62    ///
63    /// # Example
64    /// ```rust
65    /// use mathhook_core::core::polynomial::poly::IntPoly;
66    ///
67    /// let p = IntPoly::from_coeffs(vec![1, 2, 3]);
68    /// assert_eq!(p.degree(), Some(2));
69    /// ```
70    #[inline(always)]
71    pub fn from_coeffs(mut coeffs: Vec<T>) -> Self {
72        while coeffs.last().is_some_and(|c| c.is_zero()) {
73            coeffs.pop();
74        }
75        Self { coeffs }
76    }
77
78    /// Create zero polynomial
79    #[inline(always)]
80    pub fn zero() -> Self {
81        Self { coeffs: Vec::new() }
82    }
83
84    /// Create constant polynomial
85    #[inline(always)]
86    pub fn constant(c: T) -> Self {
87        if c.is_zero() {
88            Self::zero()
89        } else {
90            Self { coeffs: vec![c] }
91        }
92    }
93
94    /// Check if polynomial is zero
95    #[inline(always)]
96    pub fn is_zero(&self) -> bool {
97        self.coeffs.is_empty()
98    }
99
100    /// Check if polynomial is constant
101    #[inline(always)]
102    pub fn is_constant(&self) -> bool {
103        self.coeffs.len() <= 1
104    }
105
106    /// Get polynomial degree (None for zero polynomial)
107    #[inline(always)]
108    pub fn degree(&self) -> Option<usize> {
109        if self.coeffs.is_empty() {
110            None
111        } else {
112            Some(self.coeffs.len() - 1)
113        }
114    }
115
116    /// Get leading coefficient (zero for zero polynomial)
117    #[inline(always)]
118    pub fn leading_coeff(&self) -> T {
119        self.coeffs.last().cloned().unwrap_or_else(T::zero)
120    }
121
122    /// Get coefficient of x^i
123    #[inline(always)]
124    pub fn coeff(&self, i: usize) -> T {
125        self.coeffs.get(i).cloned().unwrap_or_else(T::zero)
126    }
127
128    /// Get all coefficients
129    #[inline(always)]
130    pub fn coefficients(&self) -> &[T] {
131        &self.coeffs
132    }
133}
134
135impl IntPoly {
136    /// Create monomial x^n (IntPoly-specific)
137    #[inline(always)]
138    pub fn monomial(n: usize) -> Self {
139        let mut coeffs = vec![0; n + 1];
140        coeffs[n] = 1;
141        Self { coeffs }
142    }
143
144    /// Create monomial c * x^n (IntPoly-specific)
145    #[inline(always)]
146    pub fn term(coeff: i64, power: usize) -> Self {
147        if coeff == 0 {
148            Self::zero()
149        } else {
150            let mut coeffs = vec![0; power + 1];
151            coeffs[power] = coeff;
152            Self { coeffs }
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_construction() {
163        let p = IntPoly::from_coeffs(vec![1, 2, 3]);
164        assert_eq!(p.degree(), Some(2));
165        assert_eq!(p.leading_coeff(), 3);
166        assert_eq!(p.coeff(0), 1);
167        assert_eq!(p.coeff(1), 2);
168        assert_eq!(p.coeff(2), 3);
169        assert_eq!(p.coeff(3), 0);
170    }
171
172    #[test]
173    fn test_zero_polynomial() {
174        let z = IntPoly::zero();
175        assert!(z.is_zero());
176        assert_eq!(z.degree(), None);
177        assert_eq!(z.leading_coeff(), 0);
178    }
179
180    #[test]
181    fn test_trailing_zeros_removed() {
182        let p = IntPoly::from_coeffs(vec![1, 2, 0, 0, 0]);
183        assert_eq!(p.degree(), Some(1));
184        assert_eq!(p.coefficients(), &[1, 2]);
185    }
186
187    #[test]
188    fn test_constant_polynomial() {
189        let p = IntPoly::constant(5);
190        assert!(p.is_constant());
191        assert_eq!(p.degree(), Some(0));
192        assert_eq!(p.coeff(0), 5);
193
194        let z = IntPoly::constant(0);
195        assert!(z.is_zero());
196        assert_eq!(z.degree(), None);
197    }
198
199    #[test]
200    fn test_rational_poly_construction() {
201        let p =
202            RationalPoly::from_coeffs(vec![Ratio::new(1, 2), Ratio::new(3, 4), Ratio::new(5, 6)]);
203        assert_eq!(p.degree(), Some(2));
204        assert_eq!(p.leading_coeff(), Ratio::new(5, 6));
205        assert_eq!(p.coeff(0), Ratio::new(1, 2));
206    }
207
208    #[test]
209    fn test_rational_poly_zero() {
210        let z = RationalPoly::zero();
211        assert!(z.is_zero());
212        assert_eq!(z.degree(), None);
213        assert_eq!(z.leading_coeff(), Ratio::new(0, 1));
214    }
215}