#![cfg(all(feature = "poly", feature = "galois"))]
use puremp::{FactorOverField, FiniteField, GaloisField, GfElement, Int, ModInt, Poly};
fn fp(v: i64, p: i64) -> ModInt {
ModInt::new(Int::from_i64(v), Int::from_i64(p))
}
fn poly_fp(coeffs: &[i64], p: i64) -> Poly<ModInt> {
Poly::new(coeffs.iter().map(|&c| fp(c, p)).collect())
}
fn check_reconstructs<T: FiniteField + core::fmt::Debug>(
factors: &[(Poly<T>, usize)],
expected: &Poly<T>,
) {
let one = expected.leading().expect("nonzero expected").one();
let mut product = Poly::constant(one);
for (f, mult) in factors {
for _ in 0..*mult {
product = product.mul(f);
}
}
assert_eq!(
product.monic(),
expected.monic(),
"product of factors must reconstruct the input"
);
}
fn check_full<T: FiniteField + core::fmt::Debug + core::fmt::Display>(
f: &Poly<T>,
) -> Vec<(Poly<T>, usize)> {
let factors = f.factor();
check_reconstructs(&factors, f);
for (g, _) in &factors {
assert!(
g.is_irreducible(),
"returned factor {g} must be irreducible"
);
}
factors
}
#[test]
fn gf5_x2_minus_1_splits_into_two_linears() {
let f = poly_fp(&[-1, 0, 1], 5);
let factors = check_full(&f);
assert_eq!(factors.len(), 2);
assert!(
factors
.iter()
.all(|(g, m)| *m == 1 && g.degree() == Some(1))
);
}
#[test]
fn gf5_x2_plus_1_is_irreducible() {
let f = poly_fp(&[1, 0, 1], 5);
let factors = check_full(&f);
assert_eq!(factors.len(), 2, "x²+1 splits over GF(5): {factors:?}");
assert!(!f.is_irreducible());
}
#[test]
fn gf5_x2_plus_2_is_irreducible() {
let f = poly_fp(&[2, 0, 1], 5);
assert!(f.is_irreducible());
let factors = check_full(&f);
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].1, 1);
}
#[test]
fn gf5_x3_minus_x_splits_completely() {
let f = poly_fp(&[0, -1, 0, 1], 5);
let factors = check_full(&f);
assert_eq!(factors.len(), 3);
assert!(
factors
.iter()
.all(|(g, m)| *m == 1 && g.degree() == Some(1))
);
}
#[test]
fn gf2_x2_plus_1_is_a_square() {
let f = poly_fp(&[1, 0, 1], 2);
let factors = check_full(&f);
assert_eq!(factors.len(), 1);
let (g, m) = &factors[0];
assert_eq!(*m, 2, "multiplicity two");
assert_eq!(g.degree(), Some(1));
let sff = f.squarefree_factorization();
assert_eq!(sff.len(), 1);
assert_eq!(sff[0].1, 2);
}
#[test]
fn gf2_x4_plus_x_plus_1_is_irreducible() {
let f = poly_fp(&[1, 1, 0, 0, 1], 2);
assert!(f.is_irreducible());
let factors = check_full(&f);
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].1, 1);
}
#[test]
fn gf2_x4_plus_x2_plus_1_is_square_of_irreducible() {
let f = poly_fp(&[1, 0, 1, 0, 1], 2);
let factors = check_full(&f);
assert_eq!(factors.len(), 1);
let (g, m) = &factors[0];
assert_eq!(*m, 2);
assert_eq!(g.degree(), Some(2));
assert!(g.is_irreducible());
}
#[test]
fn gf2_product_of_distinct_irreducibles() {
let a = poly_fp(&[1, 1], 2); let b = poly_fp(&[1, 1, 1], 2); let c = poly_fp(&[1, 1, 0, 0, 1], 2); let f = a.mul(&b).mul(&c);
let factors = check_full(&f);
assert_eq!(factors.len(), 3);
let mut degs: Vec<usize> = factors.iter().map(|(g, _)| g.degree().unwrap()).collect();
degs.sort_unstable();
assert_eq!(degs, vec![1, 2, 4]);
}
#[test]
fn gf9_product_of_two_irreducibles_comes_back() {
let field = GaloisField::create(Int::from_i64(3), 2).expect("GF(9)");
let x = |c: &[i64]| -> Poly<GfElement> {
Poly::new(
c.iter()
.map(|&v| field.from_int(&Int::from_i64(v)))
.collect(),
)
};
let tgen = field.generator(); let p1 = Poly::new(vec![tgen.clone(), field.zero(), field.one()]);
let p2 = Poly::new(vec![tgen.clone(), field.one(), field.one()]);
if !p1.is_irreducible() || !p2.is_irreducible() {
let q1 = x(&[1, 0, 1]);
let q2 = x(&[2, 1, 1]);
if q1.is_irreducible() && q2.is_irreducible() {
let f = q1.mul(&q2);
let factors = check_full(&f);
assert_eq!(factors.len(), 2);
}
return;
}
let f = p1.mul(&p2);
let factors = check_full(&f);
assert_eq!(factors.len(), 2);
assert!(
factors
.iter()
.all(|(g, m)| *m == 1 && g.degree() == Some(2))
);
}
#[test]
fn gf9_irreducible_stays_irreducible_and_square_is_detected() {
let field = GaloisField::create(Int::from_i64(3), 2).expect("GF(9)");
let g = field.generator();
let p = Poly::new(vec![g.clone(), field.zero(), field.one()]);
if p.is_irreducible() {
let sq = p.mul(&p);
let factors = check_full(&sq);
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].1, 2);
}
}
#[test]
fn gf256_product_of_two_irreducibles() {
let aes: Vec<Int> = [1, 1, 0, 1, 1, 0, 0, 0, 1]
.iter()
.map(|&c| Int::from_i64(c))
.collect();
let field = GaloisField::new(Int::from_i64(2), &aes).expect("GF(256), AES poly");
let a = field.generator(); let l1 = Poly::new(vec![a.clone(), field.one()]);
let l2 = Poly::new(vec![a.add(&field.one()), field.one()]);
let f = l1.mul(&l2);
let factors = check_full(&f);
assert_eq!(factors.len(), 2);
assert!(
factors
.iter()
.all(|(g, m)| *m == 1 && g.degree() == Some(1))
);
let quad = Poly::new(vec![a.clone(), field.one(), field.one()]);
if quad.is_irreducible() {
let factors = quad.factor();
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].1, 1);
}
}
#[test]
fn is_irreducible_known_cases() {
assert!(poly_fp(&[2, 0, 1], 5).is_irreducible());
assert!(poly_fp(&[1, 1, 0, 0, 1], 2).is_irreducible());
assert!(!poly_fp(&[-1, 0, 1], 5).is_irreducible());
assert!(!poly_fp(&[1, 0, 1], 2).is_irreducible());
assert!(!poly_fp(&[3], 7).is_irreducible()); assert!(poly_fp(&[3, 1], 7).is_irreducible());
}
#[test]
fn squarefull_over_gf7() {
let base = poly_fp(&[-1, 1], 7); let mut f = poly_fp(&[-2, 1], 7); for _ in 0..3 {
f = f.mul(&base);
}
let factors = check_full(&f);
assert_eq!(factors.len(), 2);
let mut mults: Vec<usize> = factors.iter().map(|(_, m)| *m).collect();
mults.sort_unstable();
assert_eq!(mults, vec![1, 3]);
}
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
}
#[test]
fn high_degree_gfp_factors_back() {
let p = 1_000_003;
let mut rng = Lcg(0x1234_5678);
let mut f = poly_fp(&[1], p); while f.degree().unwrap_or(0) < 120 {
let deg = 1 + (rng.next() as usize % 4);
let mut coeffs: Vec<i64> = (0..deg).map(|_| (rng.next() % p as u64) as i64).collect();
coeffs.push(1 + (rng.next() % (p as u64 - 1)) as i64); let g = poly_fp(&coeffs, p);
f = f.mul(&g);
}
let factors = check_full(&f);
assert!(!factors.is_empty());
let total: usize = factors.iter().map(|(g, m)| g.degree().unwrap() * m).sum();
assert_eq!(total, f.degree().unwrap());
}