pub(crate) mod binomial;
use super::error::FactorError;
use super::multipoly::{multi_to_flint_pub, MultiPoly};
use super::unipoly::UniPoly;
use crate::flint::mpoly::{FlintMPolyCtx, FlintMPolyFactor};
use crate::flint::nmod::{FlintNmodPoly, FlintNmodPolyFactor};
use crate::flint::FlintPoly;
use crate::kernel::ExprId;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UniPolyFactorization {
pub unit: rug::Integer,
pub factors: Vec<(UniPoly, u32)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultiPolyFactorization {
pub unit: rug::Integer,
pub factors: Vec<(MultiPoly, u32)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UniPolyFactorModP {
pub modulus: u64,
pub factors: Vec<(Vec<u64>, u32)>,
}
impl UniPolyFactorization {
pub fn expand_with_var(&self, var: ExprId) -> UniPoly {
let mut acc = FlintPoly::from_rug_coefficients(std::slice::from_ref(&self.unit));
for (f, e) in &self.factors {
let powed = f.coeffs.pow(*e);
acc = &acc * &powed;
}
UniPoly { var, coeffs: acc }
}
pub fn verifies_product(&self, original: &UniPoly) -> bool {
self.expand_with_var(original.var) == *original
}
}
impl MultiPolyFactorization {
pub fn expand_clone_vars(&self) -> MultiPoly {
let vars = self
.factors
.first()
.map(|(m, _)| m.vars.clone())
.unwrap_or_default();
let mut terms = std::collections::BTreeMap::new();
terms.insert(vec![], self.unit.clone());
let mut acc = MultiPoly { vars, terms };
for (f, e) in &self.factors {
let mut powered = MultiPoly::constant(f.vars.clone(), 1);
for _ in 0..*e {
powered = powered * f.clone();
}
acc = acc * powered;
}
acc
}
pub fn verifies_product(&self, original: &MultiPoly) -> bool {
let mut terms = std::collections::BTreeMap::new();
terms.insert(vec![], self.unit.clone());
let mut acc = MultiPoly {
vars: original.vars.clone(),
terms,
};
for (factor, exponent) in &self.factors {
if factor.vars != original.vars {
return false;
}
let mut powered = MultiPoly::constant(original.vars.clone(), 1);
for _ in 0..*exponent {
powered = powered * factor.clone();
}
acc = acc * powered;
}
acc == *original
}
}
pub fn cmp_univariate_factors(a: &(FlintPoly, u32), b: &(FlintPoly, u32)) -> std::cmp::Ordering {
a.0.cmp_canonical(&b.0).then(a.1.cmp(&b.1))
}
pub fn cmp_mod_p_factors(a: &(Vec<u64>, u32), b: &(Vec<u64>, u32)) -> std::cmp::Ordering {
a.0.len()
.cmp(&b.0.len())
.then_with(|| a.0.iter().rev().cmp(b.0.iter().rev()))
.then(a.1.cmp(&b.1))
}
fn cmp_exponents<T: Copy + Into<u64>>(a: &[T], b: &[T]) -> std::cmp::Ordering {
let n = a.len().max(b.len());
for i in 0..n {
let x: u64 = a.get(i).map_or(0, |&v| v.into());
let y: u64 = b.get(i).map_or(0, |&v| v.into());
if x != y {
return x.cmp(&y);
}
}
std::cmp::Ordering::Equal
}
fn terms_descending<T: Copy + Into<u64>>(
m: &std::collections::BTreeMap<Vec<T>, rug::Integer>,
) -> Vec<(&[T], &rug::Integer)> {
let mut v: Vec<(&[T], &rug::Integer)> = m.iter().map(|(e, c)| (e.as_slice(), c)).collect();
v.sort_by(|x, y| cmp_exponents(y.0, x.0));
v
}
pub fn cmp_multivariate_terms<T: Copy + Into<u64>>(
a: &std::collections::BTreeMap<Vec<T>, rug::Integer>,
b: &std::collections::BTreeMap<Vec<T>, rug::Integer>,
) -> std::cmp::Ordering {
let total = |m: &std::collections::BTreeMap<Vec<T>, rug::Integer>| {
m.keys()
.map(|e| e.iter().map(|&v| u128::from(v.into())).sum::<u128>())
.max()
.unwrap_or(0)
};
let by_degree = total(a).cmp(&total(b));
if by_degree.is_ne() {
return by_degree;
}
let (ta, tb) = (terms_descending(a), terms_descending(b));
let zero = rug::Integer::new();
let (mut i, mut j) = (0, 0);
while i < ta.len() || j < tb.len() {
let (ca, cb) = match (ta.get(i), tb.get(j)) {
(Some(x), Some(y)) => match cmp_exponents(x.0, y.0) {
std::cmp::Ordering::Equal => {
i += 1;
j += 1;
(x.1, y.1)
}
std::cmp::Ordering::Greater => {
i += 1;
(x.1, &zero)
}
std::cmp::Ordering::Less => {
j += 1;
(&zero, y.1)
}
},
(Some(x), None) => {
i += 1;
(x.1, &zero)
}
(None, Some(y)) => {
j += 1;
(&zero, y.1)
}
(None, None) => unreachable!(),
};
let c = ca.cmp(cb);
if c.is_ne() {
return c;
}
}
std::cmp::Ordering::Equal
}
pub fn factor_univariate_z(p: &UniPoly) -> Result<UniPolyFactorization, FactorError> {
p.factor_z()
}
pub fn factor_multivariate_z(p: &MultiPoly) -> Result<MultiPolyFactorization, FactorError> {
p.factor_z()
}
impl UniPoly {
pub fn factor_z(&self) -> Result<UniPolyFactorization, FactorError> {
if self.is_zero() {
return Err(FactorError::ZeroPolynomial);
}
let (unit, facs) = self
.coeffs
.factor_over_z()
.map_err(|()| FactorError::FlintFailure)?;
let factors: Vec<_> = facs
.into_iter()
.map(|(c, e)| {
(
UniPoly {
var: self.var,
coeffs: c,
},
e,
)
})
.collect();
Ok(UniPolyFactorization {
unit: unit.to_rug(),
factors,
})
}
}
impl MultiPoly {
pub fn factor_z(&self) -> Result<MultiPolyFactorization, FactorError> {
if self.is_zero() {
return Err(FactorError::ZeroPolynomial);
}
let nvars = self.vars.len().max(1);
let ctx = FlintMPolyCtx::new(nvars);
let a = multi_to_flint_pub(self, Arc::clone(&ctx));
let mut fac = FlintMPolyFactor::new(Arc::clone(&ctx));
if !fac.factor(&a) {
return Err(FactorError::FlintFailure);
}
if !fac.constant_den_is_one() {
return Err(FactorError::FlintFailure);
}
let unit = fac.unit().to_rug();
let mut factors = Vec::with_capacity(fac.len());
for i in fac.canonical_order() {
let base = fac.base_at(i);
let terms = base.terms();
let mp = MultiPoly {
vars: self.vars.clone(),
terms,
};
let exp = fac.exp_at(i);
factors.push((mp, exp));
}
Ok(MultiPolyFactorization { unit, factors })
}
}
pub fn factor_univariate_mod_p(
coeffs: &[i64],
modulus: u64,
) -> Result<UniPolyFactorModP, FactorError> {
factor_univariate_mod_p_with_unit(coeffs, modulus).map(|(_, fac)| fac)
}
pub fn factor_univariate_mod_p_with_unit(
coeffs: &[i64],
modulus: u64,
) -> Result<(u64, UniPolyFactorModP), FactorError> {
crate::budget::clear_trip();
if modulus < 2 || unsafe { crate::flint::ffi::n_is_prime(modulus) } == 0 {
return Err(FactorError::InvalidModulus);
}
let p = modulus as i128;
let reduced: Vec<u64> = coeffs
.iter()
.map(|&c| (c as i128).rem_euclid(p) as u64)
.collect();
let Some(last) = reduced.iter().rposition(|&c| c != 0) else {
return Err(FactorError::ZeroPolynomial);
};
let unit = reduced[last];
let words = (last as u64 + 1).saturating_mul(16);
if let Err(trip) = crate::budget::preflight_bytes(words.saturating_mul(8)) {
crate::budget::record_trip(trip);
return Err(FactorError::FlintFailure);
}
let mut poly = FlintNmodPoly::new(modulus);
for (i, &r) in reduced[..=last].iter().enumerate() {
poly.set_coeff(i, r);
}
let mut fac = FlintNmodPolyFactor::new();
fac.factor(&poly);
let mut factors: Vec<(Vec<u64>, u32)> = (0..fac.len())
.map(|i| {
let z = fac.poly_at(modulus, i);
let deg = z.degree();
let vc = (0..=deg).map(|j| z.get_coeff(j)).collect::<Vec<_>>();
(vc, fac.exp_at(i))
})
.collect();
factors.sort_by(cmp_mod_p_factors);
Ok((unit, UniPolyFactorModP { modulus, factors }))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
#[test]
fn univariate_x_squared_minus_one() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let e = pool.add(vec![pool.pow(x, pool.integer(2_i32)), pool.integer(-1_i32)]);
let p = UniPoly::from_symbolic(e, x, &pool).unwrap();
let fac = p.factor_z().unwrap();
assert_eq!(fac.factors.len(), 2);
let prod = fac.expand_with_var(x);
assert_eq!(prod, p);
assert!(fac.verifies_product(&p));
}
fn coeffs_of(f: &FlintPoly) -> Vec<i64> {
f.coefficients()
}
#[test]
fn canonical_order_x12_minus_1() {
let f = FlintPoly::from_coefficients(&[-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
let (unit, fac) = f.factor_over_z().unwrap();
assert_eq!(unit.to_rug(), 1);
let got: Vec<Vec<i64>> = fac.iter().map(|(p, _)| coeffs_of(p)).collect();
assert_eq!(
got,
vec![
vec![-1, 1],
vec![1, 1],
vec![1, -1, 1],
vec![1, 0, 1],
vec![1, 1, 1],
vec![1, 0, -1, 0, 1],
]
);
}
#[test]
fn canonical_order_is_independent_of_flint() {
let x = FlintPoly::from_coefficients(&[0, 1]);
let xp2 = FlintPoly::from_coefficients(&[2, 1]);
for n in [156usize, 168, 240] {
let mut c = vec![0i64; n + 1];
c[0] = -1;
c[n] = 1;
let f = &(&FlintPoly::from_coefficients(&c) * &xp2) * &x.pow(2);
let (unit, fac) = f.factor_over_z().unwrap();
for w in fac.windows(2) {
assert_eq!(
cmp_univariate_factors(&w[0], &w[1]),
std::cmp::Ordering::Less,
"n = {n}: not strictly sorted"
);
}
let (unit_ref, mut raw) = f.factor_over_z_flint_order().unwrap();
assert_eq!(unit, unit_ref);
raw.sort_by(cmp_univariate_factors);
assert!(raw == fac, "n = {n}");
let lin: Vec<(Vec<i64>, u32)> = fac
.iter()
.take_while(|(p, _)| p.degree() == 1)
.map(|(p, e)| (coeffs_of(p), *e))
.collect();
assert_eq!(
lin,
vec![
(vec![-1, 1], 1),
(vec![0, 1], 2),
(vec![1, 1], 1),
(vec![2, 1], 1)
]
);
}
}
#[test]
fn random_products_canonical_and_unchanged() {
let mut seed = 0x2545_f491_4f6c_dd1du64;
let mut next = move |m: i64| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed % (2 * m as u64 + 1)) as i64 - m
};
for trial in 0..200 {
let k = 1 + (next(2) + 2) as usize;
let mut parts = Vec::new();
for _ in 0..k {
let deg = 1 + (next(1) + 1) as usize;
let mut c: Vec<i64> = (0..deg).map(|_| next(4)).collect();
c.push(1 + next(1).abs());
parts.push(FlintPoly::from_coefficients(&c));
}
let scale = FlintPoly::from_coefficients(&[
next(3).max(1) * if trial % 2 == 0 { 1 } else { -1 }
]);
let mut f = scale.clone();
for p in &parts {
f = &f * p;
}
let mut g = scale;
for p in parts.iter().rev() {
g = &g * p;
}
assert!(f == g);
let (uf, ff) = f.factor_over_z().unwrap();
let (ur, mut fr) = f.factor_over_z_flint_order().unwrap();
fr.sort_by(cmp_univariate_factors);
assert!(uf == ur && ff == fr, "trial {trial}");
for w in ff.windows(2) {
assert_eq!(
cmp_univariate_factors(&w[0], &w[1]),
std::cmp::Ordering::Less
);
}
let up = UniPoly {
var: crate::kernel::ExprPool::new().symbol("x", Domain::Real),
coeffs: f.clone(),
};
assert!(
up.factor_z().unwrap().verifies_product(&up),
"trial {trial}"
);
}
}
#[test]
fn multivariate_canonical_order() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let vars = vec![x, y];
let e = pool.add(vec![
pool.pow(x, pool.integer(6_i32)),
pool.mul(vec![pool.integer(-1_i32), pool.pow(y, pool.integer(6_i32))]),
]);
let p = MultiPoly::from_symbolic(e, vars, &pool).unwrap();
let fac = p.factor_z().unwrap();
assert!(fac.verifies_product(&p));
let mut want = Vec::new();
for terms in [
vec![(vec![1u32], 1i64), (vec![0, 1], -1)],
vec![(vec![1], 1), (vec![0, 1], 1)],
vec![(vec![2], 1), (vec![1, 1], -1), (vec![0, 2], 1)],
vec![(vec![2], 1), (vec![1, 1], 1), (vec![0, 2], 1)],
] {
let t: std::collections::BTreeMap<Vec<u32>, rug::Integer> = terms
.into_iter()
.map(|(e, c)| (e, rug::Integer::from(c)))
.collect();
want.push(t);
}
let got: Vec<_> = fac.factors.iter().map(|(m, _)| m.terms.clone()).collect();
assert_eq!(got, want);
}
#[test]
fn mod_p_factors_are_sorted() {
let fac = factor_univariate_mod_p(&[-1, 0, 0, 0, 0, 0, 0, 0, 1], 17).unwrap();
let roots: Vec<u64> = fac.factors.iter().map(|(f, _)| f[0]).collect();
let mut sorted = roots.clone();
sorted.sort_unstable();
assert_eq!(roots, sorted);
assert_eq!(roots.len(), 8);
}
#[test]
fn swinnerton_dyer_irreducible_degree() {
let fp = FlintPoly::swinnerton_dyer(5);
assert_eq!(fp.degree(), 32);
let fac = fp.factor_over_z().unwrap();
assert_eq!(fac.1.len(), 1);
assert_eq!(fac.1[0].1, 1);
}
#[test]
fn cyclotomic_105_mod2_splits() {
let phi = FlintPoly::cyclotomic(105);
let coeffs: Vec<i64> = (0..phi.length())
.map(|i| {
(phi.get_coeff_flint(i).to_rug() % 2i32)
.to_i64()
.expect("coeff mod 2")
})
.collect();
let fac = factor_univariate_mod_p(&coeffs, 2).unwrap();
let deg_total: i64 = fac
.factors
.iter()
.map(|(f, e)| (f.len() as i64 - 1) * (*e as i64))
.sum();
assert_eq!(deg_total, phi.degree());
assert!(
fac.factors.len() >= 2,
"Φ_105 should have multiple factors over GF(2)"
);
}
#[test]
fn composite_modulus_is_refused_not_aborted() {
for m in [4u64, 9, 12, 15, 21, 1 << 32, u64::MAX] {
for coeffs in [&[6i64, 5, 1][..], &[1, 0, 1], &[0, 0, 1], &[1, 2, 1]] {
assert_eq!(
factor_univariate_mod_p(coeffs, m),
Err(FactorError::InvalidModulus),
"{coeffs:?} mod {m}"
);
}
}
assert_eq!(
factor_univariate_mod_p(&[1, 0, 1], 1),
Err(FactorError::InvalidModulus)
);
let big = factor_univariate_mod_p(&[1, 0, 1], 18_446_744_073_709_551_557).unwrap();
assert!(!big.factors.is_empty());
}
#[test]
fn mod_p_factorisation_keeps_the_unit() {
let (u, f) = factor_univariate_mod_p_with_unit(&[5], 7).unwrap();
assert_eq!((u, f.factors), (5, vec![]));
let (u, f) = factor_univariate_mod_p_with_unit(&[0, 2], 7).unwrap();
assert_eq!((u, f.factors), (2, vec![(vec![0, 1], 1)]));
let (u, f) = factor_univariate_mod_p_with_unit(&[2, -5], 7).unwrap();
assert_eq!(u, 2);
assert_eq!(f.factors, vec![(vec![1, 1], 1)]);
let input = [3i64, -4, 0, 6, 2];
let p = 11u64;
let (u, f) = factor_univariate_mod_p_with_unit(&input, p).unwrap();
let mul = |a: &[u64], b: &[u64]| {
let mut out = vec![0u64; a.len() + b.len() - 1];
for (i, &x) in a.iter().enumerate() {
for (j, &y) in b.iter().enumerate() {
out[i + j] = (out[i + j] + x * y) % p;
}
}
out
};
let mut prod = vec![u];
for (g, e) in &f.factors {
for _ in 0..*e {
prod = mul(&prod, g);
}
}
let want: Vec<u64> = input
.iter()
.map(|&c| c.rem_euclid(p as i64) as u64)
.collect();
assert_eq!(prod, want);
}
#[test]
fn zero_mod_p_is_the_zero_polynomial() {
for coeffs in [&[][..], &[7i64], &[0, 14, -21]] {
assert_eq!(
factor_univariate_mod_p(coeffs, 7),
Err(FactorError::ZeroPolynomial),
"{coeffs:?}"
);
}
}
#[test]
fn multivariate_product_recovered() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let vars = vec![x, y];
let f1 = MultiPoly::from_symbolic(
pool.add(vec![
pool.pow(x, pool.integer(2_i32)),
pool.pow(y, pool.integer(2_i32)),
pool.integer(-1_i32),
]),
vars.clone(),
&pool,
)
.unwrap();
let x_minus_y = pool.add(vec![x, pool.mul(vec![pool.integer(-1i32), y])]);
let f2 = MultiPoly::from_symbolic(x_minus_y, vars.clone(), &pool).unwrap();
let product = f1.clone() * f2.clone();
let fac = product.factor_z().unwrap();
let expanded = fac.expand_clone_vars();
assert_eq!(expanded, product);
assert!(fac.verifies_product(&product));
}
}