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        // Explicit work-stack rather than recursion: each split peels one
142        // linear factor off, so the recursion depth was the polynomial
143        // degree — attacker-controlled — with two `Vec<BigRational>`s and a
144        // `Vec<Vec<BigRational>>` accumulator per frame. The `Vec` return
145        // type leaves nowhere to report a depth cap, so a cap could only
146        // have dropped factors silently.
147        //
148        // Pushing the second half before the first makes the LIFO stack
149        // emit factors in the same order as the former recursion.
150        let mut factors = Vec::new();
151        let mut work = vec![poly.to_vec()];
152
153        while let Some(current) = work.pop() {
154            if current.len() <= 2 {
155                // Linear or constant - already irreducible
156                factors.push(current);
157                continue;
158            }
159
160            // Check if polynomial is irreducible
161            if self.is_irreducible(&current) {
162                factors.push(current);
163                continue;
164            }
165
166            // Attempt factorization using Kronecker substitution
167            if let Some((f1, f2)) = self.kronecker_factor(&current) {
168                work.push(f2);
169                work.push(f1);
170                continue;
171            }
172
173            // Default: return as single factor
174            factors.push(current);
175        }
176
177        factors
178    }
179
180    /// Kronecker substitution factorization attempt.
181    fn kronecker_factor(
182        &self,
183        poly: &[BigRational],
184    ) -> Option<(Vec<BigRational>, Vec<BigRational>)> {
185        // Try small integer evaluations to find potential factors
186        for x in -5..=5 {
187            let val = Self::evaluate(poly, &BigRational::from_integer(BigInt::from(x)));
188
189            if val.is_zero() {
190                // Found a root at x, factor out (t - x)
191                let linear_factor = vec![
192                    BigRational::one(),
193                    BigRational::from_integer(BigInt::from(-x)),
194                ];
195
196                let quotient = Self::divide(poly, &linear_factor);
197                return Some((linear_factor, quotient));
198            }
199        }
200
201        None
202    }
203
204    /// Hensel lifting for factorization refinement.
205    pub fn hensel_lift(
206        &mut self,
207        _poly: &[BigRational],
208        modular_factors: &[Vec<BigRational>],
209        _modulus: &BigInt,
210    ) -> Vec<Vec<BigRational>> {
211        self.stats.hensel_lifts += 1;
212
213        // Simplified: return modular factors as-is
214        modular_factors.to_vec()
215    }
216
217    /// Check if polynomial is irreducible.
218    fn is_irreducible(&self, poly: &[BigRational]) -> bool {
219        // Simplified irreducibility test
220        if poly.len() <= 2 {
221            return true;
222        }
223
224        // Check for rational roots using rational root theorem
225        !self.has_rational_root(poly)
226    }
227
228    /// Check if polynomial has a rational root.
229    fn has_rational_root(&self, poly: &[BigRational]) -> bool {
230        // Test small rationals
231        for num in -10..=10 {
232            for denom in 1..=5 {
233                let x = BigRational::new(BigInt::from(num), BigInt::from(denom));
234                let val = Self::evaluate(poly, &x);
235
236                if val.is_zero() {
237                    return true;
238                }
239            }
240        }
241
242        false
243    }
244
245    /// Polynomial derivative.
246    fn derivative(poly: &[BigRational]) -> Vec<BigRational> {
247        if poly.len() <= 1 {
248            return vec![BigRational::zero()];
249        }
250
251        let mut deriv = Vec::new();
252        let degree = poly.len() - 1;
253
254        for (i, coeff) in poly.iter().enumerate().take(degree) {
255            let power = (degree - i) as i64;
256            deriv.push(coeff * BigRational::from_integer(BigInt::from(power)));
257        }
258
259        deriv
260    }
261
262    /// Polynomial GCD (Euclidean algorithm).
263    /// Drop leading (highest-degree) zero coefficients.
264    ///
265    /// Coefficients are stored highest-degree first, so a leading zero
266    /// makes `remainder`'s `lead_div.is_zero()` guard fire and return the
267    /// dividend unchanged — which is what made the recursive `gcd` below
268    /// able to cycle forever.
269    fn strip_leading_zeros(poly: &[BigRational]) -> Vec<BigRational> {
270        let start = poly.iter().position(|c| !c.is_zero()).unwrap_or(poly.len());
271        poly[start..].to_vec()
272    }
273
274    /// Euclidean polynomial GCD.
275    ///
276    /// Iterative rather than recursive, for two reasons:
277    ///
278    /// * Depth was O(degree) with a whole `Vec<BigRational>` remainder per
279    ///   frame, on attacker-supplied polynomials, and the `Vec` return type
280    ///   has no channel for a depth error.
281    /// * More seriously, termination was not guaranteed. `remainder`
282    ///   returns the dividend unchanged when the divisor's *leading*
283    ///   coefficient is zero (its `lead_div.is_zero()` break), so
284    ///   `gcd(a, b)` could step to `gcd(b, a)` and back forever. Stripping
285    ///   leading zeros makes each `remainder` strictly reduce the length,
286    ///   which is the progress the Euclidean algorithm needs.
287    fn gcd(a: &[BigRational], b: &[BigRational]) -> Vec<BigRational> {
288        let mut a = Self::strip_leading_zeros(a);
289        let mut b = Self::strip_leading_zeros(b);
290
291        while !Self::is_zero(&b) {
292            let remainder = Self::strip_leading_zeros(&Self::remainder(&a, &b));
293            a = b;
294            b = remainder;
295        }
296
297        a
298    }
299
300    /// Polynomial division (quotient).
301    fn divide(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
302        if Self::is_zero(divisor) {
303            return vec![BigRational::zero()];
304        }
305
306        let mut quotient = Vec::new();
307        let mut remainder = dividend.to_vec();
308
309        while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
310            let lead_rem = &remainder[0];
311            let lead_div = &divisor[0];
312
313            if lead_div.is_zero() {
314                break;
315            }
316
317            let q_coeff = lead_rem / lead_div;
318            quotient.push(q_coeff.clone());
319
320            // Subtract q_coeff * divisor from remainder
321            for i in 0..divisor.len() {
322                remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
323            }
324
325            remainder.remove(0);
326        }
327
328        if quotient.is_empty() {
329            vec![BigRational::zero()]
330        } else {
331            quotient
332        }
333    }
334
335    /// Polynomial remainder.
336    fn remainder(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
337        if Self::is_zero(divisor) {
338            return dividend.to_vec();
339        }
340
341        let mut remainder = dividend.to_vec();
342
343        while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
344            let lead_rem = &remainder[0];
345            let lead_div = &divisor[0];
346
347            if lead_div.is_zero() {
348                break;
349            }
350
351            let q_coeff = lead_rem / lead_div;
352
353            for i in 0..divisor.len() {
354                remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
355            }
356
357            remainder.remove(0);
358        }
359
360        remainder
361    }
362
363    /// Evaluate polynomial at a point.
364    fn evaluate(poly: &[BigRational], x: &BigRational) -> BigRational {
365        if poly.is_empty() {
366            return BigRational::zero();
367        }
368
369        let mut result = poly[0].clone();
370        for coeff in &poly[1..] {
371            result = result * x + coeff;
372        }
373
374        result
375    }
376
377    /// Check if polynomial is zero.
378    fn is_zero(poly: &[BigRational]) -> bool {
379        poly.iter().all(|c| c.is_zero())
380    }
381
382    /// Check if polynomial is constant.
383    fn is_constant(poly: &[BigRational]) -> bool {
384        poly.len() <= 1
385    }
386
387    /// Convert polynomial to cache key.
388    fn polynomial_to_key(&self, poly: &[BigRational]) -> PolynomialKey {
389        poly.iter().map(|c| c.to_string()).collect()
390    }
391
392    /// Get statistics.
393    pub fn stats(&self) -> &FactorizationStats {
394        &self.stats
395    }
396}
397
398impl Default for PolynomialFactorizer {
399    fn default() -> Self {
400        Self::new()
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    #[test]
409    fn test_polynomial_factorizer() {
410        let factorizer = PolynomialFactorizer::new();
411        assert_eq!(factorizer.stats.factorizations, 0);
412    }
413
414    #[test]
415    fn test_derivative() {
416        // f(x) = x^2 + 2x + 1 -> f'(x) = 2x + 2
417        let poly = vec![
418            BigRational::one(),
419            BigRational::from_integer(BigInt::from(2)),
420            BigRational::one(),
421        ];
422
423        let deriv = PolynomialFactorizer::derivative(&poly);
424
425        assert_eq!(deriv.len(), 2);
426    }
427
428    #[test]
429    fn test_evaluate() {
430        // f(x) = x^2 - 1
431        let poly = vec![
432            BigRational::one(),
433            BigRational::zero(),
434            BigRational::from_integer(BigInt::from(-1)),
435        ];
436
437        let val = PolynomialFactorizer::evaluate(&poly, &BigRational::one());
438        assert_eq!(val, BigRational::zero()); // f(1) = 0
439    }
440}