#![allow(unused_assignments)]
#[allow(unused_imports)]
use crate::prelude::*;
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Zero};
pub struct PolynomialFactorizer {
cache: FxHashMap<PolynomialKey, Vec<Factor>>,
stats: FactorizationStats,
}
#[derive(Debug, Clone)]
pub struct Factor {
pub poly: Vec<BigRational>,
pub multiplicity: usize,
}
type PolynomialKey = Vec<String>;
#[derive(Debug, Clone, Default)]
pub struct FactorizationStats {
pub factorizations: usize,
pub cache_hits: usize,
pub hensel_lifts: usize,
pub square_free_decompositions: usize,
}
impl PolynomialFactorizer {
pub fn new() -> Self {
Self {
cache: FxHashMap::default(),
stats: FactorizationStats::default(),
}
}
pub fn factor_univariate(&mut self, poly: &[BigRational]) -> Vec<Factor> {
self.stats.factorizations += 1;
let key = self.polynomial_to_key(poly);
if let Some(cached) = self.cache.get(&key) {
self.stats.cache_hits += 1;
return cached.clone();
}
let square_free_factors = self.square_free_decomposition(poly);
self.stats.square_free_decompositions += 1;
let mut factors = Vec::new();
for (sf_poly, multiplicity) in square_free_factors {
let irreducible_factors = self.berlekamp_zassenhaus(&sf_poly);
for irr_factor in irreducible_factors {
factors.push(Factor {
poly: irr_factor,
multiplicity,
});
}
}
self.cache.insert(key, factors.clone());
factors
}
fn square_free_decomposition(&self, poly: &[BigRational]) -> Vec<(Vec<BigRational>, usize)> {
let mut result = Vec::new();
let mut f = poly.to_vec();
let mut df = Self::derivative(&f);
let mut g = Self::gcd(&f, &df);
let mut i = 1;
while !Self::is_constant(&g) {
let q = Self::divide(&f, &g);
let h = Self::gcd(&q, &g);
let factor = Self::divide(&q, &h);
if !Self::is_constant(&factor) {
result.push((factor, i));
}
f = g;
df = Self::derivative(&f);
g = h;
i += 1;
if i > poly.len() {
break;
}
}
if !Self::is_constant(&f) {
result.push((f, i));
}
result
}
fn berlekamp_zassenhaus(&mut self, poly: &[BigRational]) -> Vec<Vec<BigRational>> {
if poly.len() <= 2 {
return vec![poly.to_vec()];
}
if self.is_irreducible(poly) {
return vec![poly.to_vec()];
}
if let Some((f1, f2)) = self.kronecker_factor(poly) {
let mut factors = self.berlekamp_zassenhaus(&f1);
factors.extend(self.berlekamp_zassenhaus(&f2));
return factors;
}
vec![poly.to_vec()]
}
fn kronecker_factor(
&self,
poly: &[BigRational],
) -> Option<(Vec<BigRational>, Vec<BigRational>)> {
for x in -5..=5 {
let val = Self::evaluate(poly, &BigRational::from_integer(BigInt::from(x)));
if val.is_zero() {
let linear_factor = vec![
BigRational::one(),
BigRational::from_integer(BigInt::from(-x)),
];
let quotient = Self::divide(poly, &linear_factor);
return Some((linear_factor, quotient));
}
}
None
}
pub fn hensel_lift(
&mut self,
_poly: &[BigRational],
modular_factors: &[Vec<BigRational>],
_modulus: &BigInt,
) -> Vec<Vec<BigRational>> {
self.stats.hensel_lifts += 1;
modular_factors.to_vec()
}
fn is_irreducible(&self, poly: &[BigRational]) -> bool {
if poly.len() <= 2 {
return true;
}
!self.has_rational_root(poly)
}
fn has_rational_root(&self, poly: &[BigRational]) -> bool {
for num in -10..=10 {
for denom in 1..=5 {
let x = BigRational::new(BigInt::from(num), BigInt::from(denom));
let val = Self::evaluate(poly, &x);
if val.is_zero() {
return true;
}
}
}
false
}
fn derivative(poly: &[BigRational]) -> Vec<BigRational> {
if poly.len() <= 1 {
return vec![BigRational::zero()];
}
let mut deriv = Vec::new();
let degree = poly.len() - 1;
for (i, coeff) in poly.iter().enumerate().take(degree) {
let power = (degree - i) as i64;
deriv.push(coeff * BigRational::from_integer(BigInt::from(power)));
}
deriv
}
fn gcd(a: &[BigRational], b: &[BigRational]) -> Vec<BigRational> {
if Self::is_zero(b) {
return a.to_vec();
}
let remainder = Self::remainder(a, b);
Self::gcd(b, &remainder)
}
fn divide(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
if Self::is_zero(divisor) {
return vec![BigRational::zero()];
}
let mut quotient = Vec::new();
let mut remainder = dividend.to_vec();
while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
let lead_rem = &remainder[0];
let lead_div = &divisor[0];
if lead_div.is_zero() {
break;
}
let q_coeff = lead_rem / lead_div;
quotient.push(q_coeff.clone());
for i in 0..divisor.len() {
remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
}
remainder.remove(0);
}
if quotient.is_empty() {
vec![BigRational::zero()]
} else {
quotient
}
}
fn remainder(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
if Self::is_zero(divisor) {
return dividend.to_vec();
}
let mut remainder = dividend.to_vec();
while remainder.len() >= divisor.len() && !Self::is_zero(&remainder) {
let lead_rem = &remainder[0];
let lead_div = &divisor[0];
if lead_div.is_zero() {
break;
}
let q_coeff = lead_rem / lead_div;
for i in 0..divisor.len() {
remainder[i] = &remainder[i] - &q_coeff * &divisor[i];
}
remainder.remove(0);
}
remainder
}
fn evaluate(poly: &[BigRational], x: &BigRational) -> BigRational {
if poly.is_empty() {
return BigRational::zero();
}
let mut result = poly[0].clone();
for coeff in &poly[1..] {
result = result * x + coeff;
}
result
}
fn is_zero(poly: &[BigRational]) -> bool {
poly.iter().all(|c| c.is_zero())
}
fn is_constant(poly: &[BigRational]) -> bool {
poly.len() <= 1
}
fn polynomial_to_key(&self, poly: &[BigRational]) -> PolynomialKey {
poly.iter().map(|c| c.to_string()).collect()
}
pub fn stats(&self) -> &FactorizationStats {
&self.stats
}
}
impl Default for PolynomialFactorizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polynomial_factorizer() {
let factorizer = PolynomialFactorizer::new();
assert_eq!(factorizer.stats.factorizations, 0);
}
#[test]
fn test_derivative() {
let poly = vec![
BigRational::one(),
BigRational::from_integer(BigInt::from(2)),
BigRational::one(),
];
let deriv = PolynomialFactorizer::derivative(&poly);
assert_eq!(deriv.len(), 2);
}
#[test]
fn test_evaluate() {
let poly = vec![
BigRational::one(),
BigRational::zero(),
BigRational::from_integer(BigInt::from(-1)),
];
let val = PolynomialFactorizer::evaluate(&poly, &BigRational::one());
assert_eq!(val, BigRational::zero()); }
}