oxiz_math/polynomial/
factorization.rs1#![allow(unused_assignments)] #[allow(unused_imports)]
10use crate::prelude::*;
11use num_bigint::BigInt;
12use num_rational::BigRational;
13use num_traits::{One, Zero};
14
15pub struct PolynomialFactorizer {
17 cache: FxHashMap<PolynomialKey, Vec<Factor>>,
19 stats: FactorizationStats,
21}
22
23#[derive(Debug, Clone)]
25pub struct Factor {
26 pub poly: Vec<BigRational>,
28 pub multiplicity: usize,
30}
31
32type PolynomialKey = Vec<String>;
34
35#[derive(Debug, Clone, Default)]
37pub struct FactorizationStats {
38 pub factorizations: usize,
40 pub cache_hits: usize,
42 pub hensel_lifts: usize,
44 pub square_free_decompositions: usize,
46}
47
48impl PolynomialFactorizer {
49 pub fn new() -> Self {
51 Self {
52 cache: FxHashMap::default(),
53 stats: FactorizationStats::default(),
54 }
55 }
56
57 pub fn factor_univariate(&mut self, poly: &[BigRational]) -> Vec<Factor> {
59 self.stats.factorizations += 1;
60
61 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 let square_free_factors = self.square_free_decomposition(poly);
70 self.stats.square_free_decompositions += 1;
71
72 let mut factors = Vec::new();
74
75 for (sf_poly, multiplicity) in square_free_factors {
76 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 self.cache.insert(key, factors.clone());
89
90 factors
91 }
92
93 fn square_free_decomposition(&self, poly: &[BigRational]) -> Vec<(Vec<BigRational>, usize)> {
95 let mut result = Vec::new();
96
97 let mut f = poly.to_vec();
99 let mut df = Self::derivative(&f);
100
101 let mut g = Self::gcd(&f, &df);
103
104 let mut i = 1;
105
106 while !Self::is_constant(&g) {
107 let q = Self::divide(&f, &g);
109
110 let h = Self::gcd(&q, &g);
112
113 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 if i > poly.len() {
127 break;
128 }
129 }
130
131 if !Self::is_constant(&f) {
133 result.push((f, i));
134 }
135
136 result
137 }
138
139 fn berlekamp_zassenhaus(&mut self, poly: &[BigRational]) -> Vec<Vec<BigRational>> {
141 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 factors.push(current);
157 continue;
158 }
159
160 if self.is_irreducible(¤t) {
162 factors.push(current);
163 continue;
164 }
165
166 if let Some((f1, f2)) = self.kronecker_factor(¤t) {
168 work.push(f2);
169 work.push(f1);
170 continue;
171 }
172
173 factors.push(current);
175 }
176
177 factors
178 }
179
180 fn kronecker_factor(
182 &self,
183 poly: &[BigRational],
184 ) -> Option<(Vec<BigRational>, Vec<BigRational>)> {
185 for x in -5..=5 {
187 let val = Self::evaluate(poly, &BigRational::from_integer(BigInt::from(x)));
188
189 if val.is_zero() {
190 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 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 modular_factors.to_vec()
215 }
216
217 fn is_irreducible(&self, poly: &[BigRational]) -> bool {
219 if poly.len() <= 2 {
221 return true;
222 }
223
224 !self.has_rational_root(poly)
226 }
227
228 fn has_rational_root(&self, poly: &[BigRational]) -> bool {
230 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 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 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 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 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 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 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 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 fn is_zero(poly: &[BigRational]) -> bool {
379 poly.iter().all(|c| c.is_zero())
380 }
381
382 fn is_constant(poly: &[BigRational]) -> bool {
384 poly.len() <= 1
385 }
386
387 fn polynomial_to_key(&self, poly: &[BigRational]) -> PolynomialKey {
389 poly.iter().map(|c| c.to_string()).collect()
390 }
391
392 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 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 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()); }
440}