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}