Skip to main content

oxiz_math/polynomial/
factorization.rs

1//! Polynomial Factorization Algorithms.
2#![allow(unused_assignments)] // Algorithm placeholder
3//!
4//! Implements multivariate polynomial factorization including:
5//! - Berlekamp-Zassenhaus algorithm
6//! - Hensel lifting
7//! - Multivariate factorization via Kronecker substitution
8
9#[allow(unused_imports)]
10use crate::prelude::*;
11use num_bigint::BigInt;
12use num_rational::BigRational;
13use num_traits::{One, Zero};
14
15/// Polynomial factorization engine.
16pub struct PolynomialFactorizer {
17    /// Factorization cache
18    cache: FxHashMap<PolynomialKey, Vec<Factor>>,
19    /// Statistics
20    stats: FactorizationStats,
21}
22
23/// A polynomial factor with multiplicity.
24#[derive(Debug, Clone)]
25pub struct Factor {
26    /// The polynomial factor
27    pub poly: Vec<BigRational>,
28    /// Multiplicity
29    pub multiplicity: usize,
30}
31
32/// Simplified polynomial key for caching.
33type PolynomialKey = Vec<String>;
34
35/// Factorization statistics.
36#[derive(Debug, Clone, Default)]
37pub struct FactorizationStats {
38    /// Number of factorizations performed
39    pub factorizations: usize,
40    /// Cache hits
41    pub cache_hits: usize,
42    /// Hensel lifts performed
43    pub hensel_lifts: usize,
44    /// Square-free decompositions
45    pub square_free_decompositions: usize,
46}
47
48impl PolynomialFactorizer {
49    /// Create a new polynomial factorizer.
50    pub fn new() -> Self {
51        Self {
52            cache: FxHashMap::default(),
53            stats: FactorizationStats::default(),
54        }
55    }
56
57    /// Factor a univariate polynomial over rationals.
58    pub fn factor_univariate(&mut self, poly: &[BigRational]) -> Vec<Factor> {
59        self.stats.factorizations += 1;
60
61        // Check cache
62        let key = self.polynomial_to_key(poly);
63        if let Some(cached) = self.cache.get(&key) {
64            self.stats.cache_hits += 1;
65            return cached.clone();
66        }
67
68        // Step 1: Square-free decomposition
69        let square_free_factors = self.square_free_decomposition(poly);
70        self.stats.square_free_decompositions += 1;
71
72        // Step 2: Factor each square-free component
73        let mut factors = Vec::new();
74
75        for (sf_poly, multiplicity) in square_free_factors {
76            // Factor over integers using Berlekamp-Zassenhaus
77            let irreducible_factors = self.berlekamp_zassenhaus(&sf_poly);
78
79            for irr_factor in irreducible_factors {
80                factors.push(Factor {
81                    poly: irr_factor,
82                    multiplicity,
83                });
84            }
85        }
86
87        // Cache result
88        self.cache.insert(key, factors.clone());
89
90        factors
91    }
92
93    /// Square-free decomposition using Yun's algorithm.
94    fn square_free_decomposition(&self, poly: &[BigRational]) -> Vec<(Vec<BigRational>, usize)> {
95        let mut result = Vec::new();
96
97        // Compute derivative
98        let mut f = poly.to_vec();
99        let mut df = Self::derivative(&f);
100
101        // GCD of f and f'
102        let mut g = Self::gcd(&f, &df);
103
104        let mut i = 1;
105
106        while !Self::is_constant(&g) {
107            // f / g
108            let q = Self::divide(&f, &g);
109
110            // GCD(q, g)
111            let h = Self::gcd(&q, &g);
112
113            // q / h is the i-th square-free factor
114            let factor = Self::divide(&q, &h);
115
116            if !Self::is_constant(&factor) {
117                result.push((factor, i));
118            }
119
120            f = g;
121            df = Self::derivative(&f);
122            g = h;
123            i += 1;
124
125            // Prevent infinite loop
126            if i > poly.len() {
127                break;
128            }
129        }
130
131        // Remaining part
132        if !Self::is_constant(&f) {
133            result.push((f, i));
134        }
135
136        result
137    }
138
139    /// Berlekamp-Zassenhaus factorization algorithm.
140    fn berlekamp_zassenhaus(&mut self, poly: &[BigRational]) -> Vec<Vec<BigRational>> {
141        // Simplified implementation
142        if poly.len() <= 2 {
143            // Linear or constant - already irreducible
144            return vec![poly.to_vec()];
145        }
146
147        // Check if polynomial is irreducible
148        if self.is_irreducible(poly) {
149            return vec![poly.to_vec()];
150        }
151
152        // Attempt factorization using Kronecker substitution
153        if let Some((f1, f2)) = self.kronecker_factor(poly) {
154            let mut factors = self.berlekamp_zassenhaus(&f1);
155            factors.extend(self.berlekamp_zassenhaus(&f2));
156            return factors;
157        }
158
159        // Default: return as single factor
160        vec![poly.to_vec()]
161    }
162
163    /// Kronecker substitution factorization attempt.
164    fn kronecker_factor(
165        &self,
166        poly: &[BigRational],
167    ) -> Option<(Vec<BigRational>, Vec<BigRational>)> {
168        // Try small integer evaluations to find potential factors
169        for x in -5..=5 {
170            let val = Self::evaluate(poly, &BigRational::from_integer(BigInt::from(x)));
171
172            if val.is_zero() {
173                // Found a root at x, factor out (t - x)
174                let linear_factor = vec![
175                    BigRational::one(),
176                    BigRational::from_integer(BigInt::from(-x)),
177                ];
178
179                let quotient = Self::divide(poly, &linear_factor);
180                return Some((linear_factor, quotient));
181            }
182        }
183
184        None
185    }
186
187    /// Hensel lifting for factorization refinement.
188    pub fn hensel_lift(
189        &mut self,
190        _poly: &[BigRational],
191        modular_factors: &[Vec<BigRational>],
192        _modulus: &BigInt,
193    ) -> Vec<Vec<BigRational>> {
194        self.stats.hensel_lifts += 1;
195
196        // Simplified: return modular factors as-is
197        modular_factors.to_vec()
198    }
199
200    /// Check if polynomial is irreducible.
201    fn is_irreducible(&self, poly: &[BigRational]) -> bool {
202        // Simplified irreducibility test
203        if poly.len() <= 2 {
204            return true;
205        }
206
207        // Check for rational roots using rational root theorem
208        !self.has_rational_root(poly)
209    }
210
211    /// Check if polynomial has a rational root.
212    fn has_rational_root(&self, poly: &[BigRational]) -> bool {
213        // Test small rationals
214        for num in -10..=10 {
215            for denom in 1..=5 {
216                let x = BigRational::new(BigInt::from(num), BigInt::from(denom));
217                let val = Self::evaluate(poly, &x);
218
219                if val.is_zero() {
220                    return true;
221                }
222            }
223        }
224
225        false
226    }
227
228    /// Polynomial derivative.
229    fn derivative(poly: &[BigRational]) -> Vec<BigRational> {
230        if poly.len() <= 1 {
231            return vec![BigRational::zero()];
232        }
233
234        let mut deriv = Vec::new();
235        let degree = poly.len() - 1;
236
237        for (i, coeff) in poly.iter().enumerate().take(degree) {
238            let power = (degree - i) as i64;
239            deriv.push(coeff * BigRational::from_integer(BigInt::from(power)));
240        }
241
242        deriv
243    }
244
245    /// Polynomial GCD (Euclidean algorithm).
246    fn gcd(a: &[BigRational], b: &[BigRational]) -> Vec<BigRational> {
247        if Self::is_zero(b) {
248            return a.to_vec();
249        }
250
251        let remainder = Self::remainder(a, b);
252        Self::gcd(b, &remainder)
253    }
254
255    /// Polynomial division (quotient).
256    fn divide(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
257        if Self::is_zero(divisor) {
258            return vec![BigRational::zero()];
259        }
260
261        let mut quotient = Vec::new();
262        let mut remainder = dividend.to_vec();
263
264        while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
265            let lead_rem = &remainder[0];
266            let lead_div = &divisor[0];
267
268            if lead_div.is_zero() {
269                break;
270            }
271
272            let q_coeff = lead_rem / lead_div;
273            quotient.push(q_coeff.clone());
274
275            // Subtract q_coeff * divisor from remainder
276            for i in 0..divisor.len() {
277                remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
278            }
279
280            remainder.remove(0);
281        }
282
283        if quotient.is_empty() {
284            vec![BigRational::zero()]
285        } else {
286            quotient
287        }
288    }
289
290    /// Polynomial remainder.
291    fn remainder(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
292        if Self::is_zero(divisor) {
293            return dividend.to_vec();
294        }
295
296        let mut remainder = dividend.to_vec();
297
298        while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
299            let lead_rem = &remainder[0];
300            let lead_div = &divisor[0];
301
302            if lead_div.is_zero() {
303                break;
304            }
305
306            let q_coeff = lead_rem / lead_div;
307
308            for i in 0..divisor.len() {
309                remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
310            }
311
312            remainder.remove(0);
313        }
314
315        remainder
316    }
317
318    /// Evaluate polynomial at a point.
319    fn evaluate(poly: &[BigRational], x: &BigRational) -> BigRational {
320        if poly.is_empty() {
321            return BigRational::zero();
322        }
323
324        let mut result = poly[0].clone();
325        for coeff in &poly[1..] {
326            result = result * x + coeff;
327        }
328
329        result
330    }
331
332    /// Check if polynomial is zero.
333    fn is_zero(poly: &[BigRational]) -> bool {
334        poly.iter().all(|c| c.is_zero())
335    }
336
337    /// Check if polynomial is constant.
338    fn is_constant(poly: &[BigRational]) -> bool {
339        poly.len() <= 1
340    }
341
342    /// Convert polynomial to cache key.
343    fn polynomial_to_key(&self, poly: &[BigRational]) -> PolynomialKey {
344        poly.iter().map(|c| c.to_string()).collect()
345    }
346
347    /// Get statistics.
348    pub fn stats(&self) -> &FactorizationStats {
349        &self.stats
350    }
351}
352
353impl Default for PolynomialFactorizer {
354    fn default() -> Self {
355        Self::new()
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362
363    #[test]
364    fn test_polynomial_factorizer() {
365        let factorizer = PolynomialFactorizer::new();
366        assert_eq!(factorizer.stats.factorizations, 0);
367    }
368
369    #[test]
370    fn test_derivative() {
371        // f(x) = x^2 + 2x + 1 -> f'(x) = 2x + 2
372        let poly = vec![
373            BigRational::one(),
374            BigRational::from_integer(BigInt::from(2)),
375            BigRational::one(),
376        ];
377
378        let deriv = PolynomialFactorizer::derivative(&poly);
379
380        assert_eq!(deriv.len(), 2);
381    }
382
383    #[test]
384    fn test_evaluate() {
385        // f(x) = x^2 - 1
386        let poly = vec![
387            BigRational::one(),
388            BigRational::zero(),
389            BigRational::from_integer(BigInt::from(-1)),
390        ];
391
392        let val = PolynomialFactorizer::evaluate(&poly, &BigRational::one());
393        assert_eq!(val, BigRational::zero()); // f(1) = 0
394    }
395}