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 if poly.len() <= 2 {
143 return vec![poly.to_vec()];
145 }
146
147 if self.is_irreducible(poly) {
149 return vec![poly.to_vec()];
150 }
151
152 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 vec![poly.to_vec()]
161 }
162
163 fn kronecker_factor(
165 &self,
166 poly: &[BigRational],
167 ) -> Option<(Vec<BigRational>, Vec<BigRational>)> {
168 for x in -5..=5 {
170 let val = Self::evaluate(poly, &BigRational::from_integer(BigInt::from(x)));
171
172 if val.is_zero() {
173 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 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 modular_factors.to_vec()
198 }
199
200 fn is_irreducible(&self, poly: &[BigRational]) -> bool {
202 if poly.len() <= 2 {
204 return true;
205 }
206
207 !self.has_rational_root(poly)
209 }
210
211 fn has_rational_root(&self, poly: &[BigRational]) -> bool {
213 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 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 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 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 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 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 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 fn is_zero(poly: &[BigRational]) -> bool {
334 poly.iter().all(|c| c.is_zero())
335 }
336
337 fn is_constant(poly: &[BigRational]) -> bool {
339 poly.len() <= 1
340 }
341
342 fn polynomial_to_key(&self, poly: &[BigRational]) -> PolynomialKey {
344 poly.iter().map(|c| c.to_string()).collect()
345 }
346
347 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 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 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()); }
395}