Skip to main content

ocas_poly/factor/
mod.rs

1//! Polynomial factorization algorithms.
2//!
3//! This module groups square-free factorization with complete factorization
4//! over finite fields ([`finite_field`]) and, for lifting back to the integers,
5//! Hensel lifting ([`hensel`]).
6//!
7//! The top-level entry point for factoring a univariate polynomial over
8//! $\mathbb{Z}$ is [`DenseUnivariatePolynomial::factor`](crate::DenseUnivariatePolynomial::factor),
9//! and over a finite field
10//! [`factor_over_finite_field`](finite_field::factor_over_finite_field).
11
12use ocas_domain::EuclideanDomain;
13use ocas_domain::{FiniteField, IntegerDomain};
14
15use crate::dense::DenseUnivariatePolynomial;
16
17pub mod algebraic;
18pub mod eez;
19pub mod finite_field;
20pub mod hensel;
21pub mod multivariate;
22
23/// Result of a square-free factorization: list of (factor, multiplicity) pairs.
24pub type SquareFreeFactors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>;
25
26/// Result of a complete factorization: list of (factor, multiplicity) pairs
27/// where each factor is irreducible (or, over the integers, primitive and
28/// irreducible over $\mathbb{Q}$).
29pub type Factors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>;
30
31impl<D: EuclideanDomain> DenseUnivariatePolynomial<D> {
32    /// Compute the square-free factorization of this polynomial.
33    ///
34    /// Returns a list of (factor, multiplicity) pairs.
35    /// For example, `(x+1)^2 * (x-1)` yields `[(x+1, 2), (x-1, 1)]`.
36    ///
37    /// # Example
38    ///
39    /// ```
40    /// use ocas_domain::{IntegerDomain, Integer};
41    /// use ocas_poly::DenseUnivariatePolynomial;
42    ///
43    /// let d = IntegerDomain;
44    /// // (x+1)^2*(x-1) = x^3 + x^2 - x - 1
45    /// let p = DenseUnivariatePolynomial::from_coeffs(d, vec![
46    ///     Integer::from(-1), Integer::from(-1), Integer::from(1), Integer::from(1),
47    /// ]);
48    /// let factors = p.square_free_factorization();
49    /// assert_eq!(factors.len(), 2);
50    /// ```
51    pub fn square_free_factorization(&self) -> SquareFreeFactors<D> {
52        let mut factors = SquareFreeFactors::new();
53        if self.is_zero() {
54            return factors;
55        }
56
57        // Step 1: make polynomial primitive.
58        let f = self.primitive_part();
59        let f_deriv = f.derivative();
60
61        // g = gcd(f, f')
62        let mut g = f.gcd(&f_deriv);
63        if g.is_zero() {
64            return factors;
65        }
66
67        // w = f / g contains each distinct irreducible factor exactly once.
68        let mut w = match f.div_rem(&g) {
69            Some((q, _)) => q,
70            None => return factors,
71        };
72
73        let mut k = 1;
74        while !w.is_one() {
75            // h = gcd(w, g)
76            let h = w.gcd(&g);
77            // z = w / h is the factor with multiplicity k.
78            if let Some((z, _)) = w.div_rem(&h)
79                && !z.is_one()
80                && !z.is_zero()
81            {
82                factors.push((z, k));
83            }
84
85            // Prepare for next iteration.
86            if let Some((q, _)) = g.div_rem(&h) {
87                g = q;
88            } else {
89                break;
90            }
91            w = h;
92            k += 1;
93        }
94
95        factors
96    }
97
98    /// Check whether this polynomial is square-free.
99    ///
100    /// A polynomial is square-free if gcd(p, p') = 1.
101    pub fn is_square_free(&self) -> bool {
102        if self.degree().unwrap_or(0) <= 1 {
103            return true;
104        }
105        let deriv = self.derivative();
106        let g = self.gcd(&deriv);
107        g.degree() == Some(0)
108    }
109}
110
111// ── factor() for integer polynomials ──────────────────────────────
112
113impl DenseUnivariatePolynomial<IntegerDomain> {
114    /// Completely factor this primitive integer polynomial into monic
115    /// irreducible factors with multiplicities.
116    ///
117    /// The input must be primitive (coefficient content = 1). Use
118    /// [`primitive_part`](crate::DenseUnivariatePolynomial::primitive_part)
119    /// to prepare an arbitrary integer polynomial before factoring.
120    ///
121    /// # Example
122    ///
123    /// ```
124    /// use ocas_domain::{Integer, IntegerDomain};
125    /// use ocas_poly::DenseUnivariatePolynomial;
126    ///
127    /// let d = IntegerDomain;
128    /// // x^2 - 1 = (x-1)(x+1)
129    /// let p = DenseUnivariatePolynomial::from_coeffs(d, vec![
130    ///     Integer::from(-1), Integer::from(0), Integer::from(1),
131    /// ]);
132    /// let factors = p.factor();
133    /// assert_eq!(factors.len(), 2);
134    /// ```
135    pub fn factor(&self) -> Factors<IntegerDomain> {
136        hensel::factor_primitive(self)
137    }
138}
139
140// ── factor() for finite-field polynomials ─────────────────────────
141
142impl DenseUnivariatePolynomial<FiniteField> {
143    /// Completely factor this univariate polynomial over $\mathbb{F}_p$
144    /// into monic irreducible factors with multiplicities.
145    ///
146    /// # Example
147    ///
148    /// ```
149    /// use num_bigint::BigInt;
150    /// use ocas_domain::{Domain, FiniteField};
151    /// use ocas_poly::DenseUnivariatePolynomial;
152    ///
153    /// let f = FiniteField::new(BigInt::from(5));
154    /// // x^2 - 1 over F_5
155    /// let p = DenseUnivariatePolynomial::from_coeffs(
156    ///     f.clone(), vec![f.element(4), f.element(0), f.element(1)]);
157    /// let factors = p.factor();
158    /// assert!(!factors.is_empty());
159    /// ```
160    pub fn factor(&self) -> Factors<FiniteField> {
161        finite_field::factor_over_finite_field(self)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use ocas_domain::{Integer, IntegerDomain};
169
170    fn i(n: i64) -> Integer {
171        Integer::from(n)
172    }
173
174    #[test]
175    fn square_free_x_plus_1_cubed() {
176        let d = IntegerDomain;
177        // (x+1)^3 = x^3 + 3x^2 + 3x + 1
178        let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(3), i(3), i(1)]);
179        let factors = p.square_free_factorization();
180        assert!(!factors.is_empty());
181        for (factor, mult) in &factors {
182            if factor.degree() == Some(1) {
183                assert_eq!(*mult, 3);
184            }
185        }
186    }
187
188    #[test]
189    fn is_square_free_linear() {
190        let d = IntegerDomain;
191        let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(1)]); // x+1
192        assert!(p.is_square_free());
193    }
194
195    #[test]
196    fn is_not_square_free_perfect_square() {
197        let d = IntegerDomain;
198        let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(2), i(1)]); // (x+1)^2
199        assert!(!p.is_square_free());
200    }
201
202    #[test]
203    fn square_free_x2_minus_1() {
204        let d = IntegerDomain;
205        let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]); // x^2-1
206        let factors = p.square_free_factorization();
207        assert!(p.is_square_free());
208        assert!(!factors.is_empty());
209    }
210}