use alloc::vec;
use alloc::vec::Vec;
use crate::int::Int;
#[inline]
fn mulmod(a: u64, b: u64, p: u64) -> u64 {
((a as u128 * b as u128) % p as u128) as u64
}
#[inline]
fn addmod(a: u64, b: u64, p: u64) -> u64 {
let s = a + b;
if s >= p { s - p } else { s }
}
#[inline]
fn submod(a: u64, b: u64, p: u64) -> u64 {
if a >= b { a - b } else { a + p - b }
}
fn powmod(mut a: u64, mut e: u64, p: u64) -> u64 {
let mut r = 1u64 % p;
a %= p;
while e > 0 {
if e & 1 == 1 {
r = mulmod(r, a, p);
}
a = mulmod(a, a, p);
e >>= 1;
}
r
}
fn invmod(a: u64, p: u64) -> u64 {
powmod(a, p - 2, p)
}
fn trim(mut a: Vec<u64>) -> Vec<u64> {
while a.last() == Some(&0) {
a.pop();
}
a
}
fn deg(a: &[u64]) -> isize {
a.len() as isize - 1
}
fn fp_sub(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
let n = a.len().max(b.len());
let mut r = vec![0u64; n];
for (i, slot) in r.iter_mut().enumerate() {
let x = *a.get(i).unwrap_or(&0);
let y = *b.get(i).unwrap_or(&0);
*slot = submod(x, y, p);
}
trim(r)
}
fn fp_mul(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r = vec![0u64; a.len() + b.len() - 1];
for (i, &ai) in a.iter().enumerate() {
if ai == 0 {
continue;
}
for (j, &bj) in b.iter().enumerate() {
r[i + j] = addmod(r[i + j], mulmod(ai, bj, p), p);
}
}
trim(r)
}
fn fp_scale(a: &[u64], s: u64, p: u64) -> Vec<u64> {
trim(a.iter().map(|&x| mulmod(x, s, p)).collect())
}
fn fp_monic(a: &[u64], p: u64) -> Vec<u64> {
match a.last() {
None => Vec::new(),
Some(&lc) => {
if lc == 1 {
a.to_vec()
} else {
fp_scale(a, invmod(lc, p), p)
}
}
}
}
fn fp_divmod(a: &[u64], b: &[u64], p: u64) -> (Vec<u64>, Vec<u64>) {
let mut r = a.to_vec();
let db = deg(b);
if deg(&r) < db {
return (Vec::new(), trim(r));
}
let inv_lc = invmod(*b.last().unwrap(), p);
let mut q = vec![0u64; (deg(&r) - db + 1) as usize];
while deg(&r) >= db && !r.is_empty() {
let d = (deg(&r) - db) as usize;
let coef = mulmod(*r.last().unwrap(), inv_lc, p);
q[d] = coef;
for (j, &bj) in b.iter().enumerate() {
r[d + j] = submod(r[d + j], mulmod(coef, bj, p), p);
}
r = trim(r);
}
(trim(q), r)
}
fn fp_rem(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
fp_divmod(a, b, p).1
}
fn fp_gcd(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
let mut a = trim(a.to_vec());
let mut b = trim(b.to_vec());
while !b.is_empty() {
let r = fp_rem(&a, &b, p);
a = b;
b = r;
}
fp_monic(&a, p)
}
fn fp_powmod(base: &[u64], mut e: u64, modulus: &[u64], p: u64) -> Vec<u64> {
let mut r = vec![1u64 % p];
let mut b = fp_rem(base, modulus, p);
while e > 0 {
if e & 1 == 1 {
r = fp_rem(&fp_mul(&r, &b, p), modulus, p);
}
b = fp_rem(&fp_mul(&b, &b, p), modulus, p);
e >>= 1;
}
trim(r)
}
fn fp_derivative(a: &[u64], p: u64) -> Vec<u64> {
if a.len() <= 1 {
return Vec::new();
}
let mut r = vec![0u64; a.len() - 1];
for i in 1..a.len() {
r[i - 1] = mulmod(a[i], (i as u64) % p, p);
}
trim(r)
}
fn distinct_degree(f: &[u64], p: u64) -> Vec<(usize, Vec<u64>)> {
let mut out = Vec::new();
let mut fstar = f.to_vec();
let mut d = 1usize;
let mut xp = fp_powmod(&[0, 1], p, &fstar, p); while deg(&fstar) >= 2 * d as isize {
let g = fp_gcd(&fstar, &fp_sub(&xp, &[0, 1], p), p);
if deg(&g) > 0 {
out.push((d, g.clone()));
fstar = fp_divmod(&fstar, &g, p).0;
}
d += 1;
if deg(&fstar) >= 2 * d as isize {
xp = fp_powmod(&xp, p, &fstar, p); }
}
if deg(&fstar) > 0 {
out.push((deg(&fstar) as usize, fstar));
}
out
}
fn equal_degree(f: &[u64], d: usize, p: u64) -> Vec<Vec<u64>> {
let r = (deg(f) / d as isize) as usize; if r == 1 {
return vec![fp_monic(f, p)];
}
let mut factors = vec![fp_monic(f, p)];
let mut seed = f.iter().fold(0x9E37_79B9_7F4A_7C15u64, |s, &c| {
s.wrapping_mul(6364136223846793005).wrapping_add(c | 1)
});
let mut next = || {
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
seed
};
let pd_half = pd_minus_one_over_two(p, d);
while factors.len() < r {
let n = f.len() - 1;
let a: Vec<u64> = (0..n).map(|_| next() % p).collect();
let a = trim(a);
if a.is_empty() {
continue;
}
let b = fp_sub(&fp_powmod(&a, pd_half, f, p), &[1], p);
let mut new_factors = Vec::with_capacity(factors.len());
for h in factors.into_iter() {
if deg(&h) == d as isize {
new_factors.push(h);
continue;
}
let g = fp_gcd(&h, &b, p);
if deg(&g) > 0 && deg(&g) < deg(&h) {
let other = fp_divmod(&h, &g, p).0;
new_factors.push(fp_monic(&g, p));
new_factors.push(fp_monic(&other, p));
} else {
new_factors.push(h);
}
}
factors = new_factors;
}
factors
}
fn pd_minus_one_over_two(p: u64, d: usize) -> u64 {
let mut pd = 1u128;
for _ in 0..d {
pd *= p as u128;
}
((pd - 1) / 2) as u64
}
fn factor_mod_p(f: &[u64], p: u64) -> Vec<Vec<u64>> {
let mut out = Vec::new();
for (d, g) in distinct_degree(f, p) {
out.extend(equal_degree(&g, d, p));
}
out
}
fn fp_bezout(a: &[u64], b: &[u64], p: u64) -> (Vec<u64>, Vec<u64>) {
let (mut r0, mut r1) = (a.to_vec(), b.to_vec());
let (mut s0, mut s1) = (vec![1u64 % p], Vec::new());
let (mut t0, mut t1) = (Vec::new(), vec![1u64 % p]);
while !r1.is_empty() {
let (q, r) = fp_divmod(&r0, &r1, p);
r0 = r1;
r1 = r;
let s = fp_sub(&s0, &fp_mul(&q, &s1, p), p);
s0 = s1;
s1 = s;
let t = fp_sub(&t0, &fp_mul(&q, &t1, p), p);
t0 = t1;
t1 = t;
}
let inv = invmod(*r0.last().unwrap(), p);
(fp_scale(&s0, inv, p), fp_scale(&t0, inv, p))
}
fn ip_trim(mut a: Vec<Int>) -> Vec<Int> {
while a.last().is_some_and(Int::is_zero) {
a.pop();
}
a
}
fn ip_mul(a: &[Int], b: &[Int]) -> Vec<Int> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r = vec![Int::ZERO; a.len() + b.len() - 1];
for (i, ai) in a.iter().enumerate() {
for (j, bj) in b.iter().enumerate() {
r[i + j] = r[i + j].add(&ai.mul(bj));
}
}
ip_trim(r)
}
fn ip_sub(a: &[Int], b: &[Int]) -> Vec<Int> {
let n = a.len().max(b.len());
let mut r = vec![Int::ZERO; n];
for (i, slot) in r.iter_mut().enumerate() {
let x = a.get(i).cloned().unwrap_or(Int::ZERO);
let y = b.get(i).cloned().unwrap_or(Int::ZERO);
*slot = x.sub(&y);
}
ip_trim(r)
}
fn ip_divmod_monic(a: &[Int], b: &[Int]) -> (Vec<Int>, Vec<Int>) {
debug_assert!(
b.last().map(|c| c == &Int::ONE).unwrap_or(false),
"divisor must be monic"
);
let db = b.len() as isize - 1;
let mut r = a.to_vec();
if (r.len() as isize - 1) < db {
return (Vec::new(), ip_trim(r));
}
let mut q = vec![Int::ZERO; (r.len() as isize - db) as usize];
while (r.len() as isize - 1) >= db && !r.is_empty() {
let d = (r.len() as isize - 1 - db) as usize;
let coef = r.last().unwrap().clone();
for (j, bj) in b.iter().enumerate() {
r[d + j] = r[d + j].sub(&coef.mul(bj));
}
q[d] = coef;
r = ip_trim(r);
}
(ip_trim(q), ip_trim(r))
}
fn ip_sym_mod(a: &[Int], m: &Int) -> Vec<Int> {
let half = m.div_floor(&Int::from_i64(2));
ip_trim(
a.iter()
.map(|c| {
let r = c.rem_euclid(m);
if r.cmp(&half) == core::cmp::Ordering::Greater {
r.sub(m)
} else {
r
}
})
.collect(),
)
}
fn fp_to_ip(a: &[u64]) -> Vec<Int> {
a.iter().map(|&c| Int::from_u64(c)).collect()
}
fn ip_content(a: &[Int]) -> Int {
a.iter().fold(Int::ZERO, |g, c| g.gcd(c))
}
fn ip_primitive(a: &[Int]) -> Vec<Int> {
let c = ip_content(a);
if c.is_zero() || c == Int::ONE {
return a.to_vec();
}
a.iter().map(|x| x.div_exact(&c)).collect()
}
fn hensel_lift_two(f: &[Int], g0: &[u64], h0: &[u64], p: u64, exp: u32) -> (Vec<Int>, Vec<Int>) {
let (s, _t) = fp_bezout(g0, h0, p);
let mut g = fp_to_ip(g0);
let mut h = fp_to_ip(h0);
let mut pk = Int::from_u64(p);
for _ in 1..exp {
let e = ip_sub(f, &ip_mul(&g, &h));
let ebar_fp: Vec<u64> = trim(
e.iter()
.map(|c| {
let q = c.div_exact(&pk); q.rem_euclid(&Int::from_u64(p)).to_u64().unwrap_or(0)
})
.collect(),
);
let u = fp_rem(&fp_mul(&s, &ebar_fp, p), h0, p);
let v = fp_divmod(&fp_sub(&ebar_fp, &fp_mul(g0, &u, p), p), h0, p).0;
g = ip_add_scaled(&g, &v, &pk);
h = ip_add_scaled(&h, &u, &pk);
pk = pk.mul(&Int::from_u64(p));
}
(g, h)
}
fn ip_add_scaled(a: &[Int], b: &[u64], scale: &Int) -> Vec<Int> {
let n = a.len().max(b.len());
let mut r = vec![Int::ZERO; n];
for (i, slot) in r.iter_mut().enumerate() {
let x = a.get(i).cloned().unwrap_or(Int::ZERO);
let add = b
.get(i)
.map(|&c| Int::from_u64(c).mul(scale))
.unwrap_or(Int::ZERO);
*slot = x.add(&add);
}
ip_trim(r)
}
fn lift_all(f: &[Int], modfacs: &[Vec<u64>], p: u64, exp: u32, m: &Int) -> Vec<Vec<Int>> {
if modfacs.len() == 1 {
return vec![ip_sym_mod(f, m)];
}
let mid = modfacs.len() / 2;
let gl = modfacs[..mid]
.iter()
.fold(vec![1u64 % p], |acc, g| fp_mul(&acc, g, p));
let hr = modfacs[mid..]
.iter()
.fold(vec![1u64 % p], |acc, g| fp_mul(&acc, g, p));
let (g, h) = hensel_lift_two(f, &gl, &hr, p, exp);
let mut out = lift_all(&ip_sym_mod(&g, m), &modfacs[..mid], p, exp, m);
out.extend(lift_all(&ip_sym_mod(&h, m), &modfacs[mid..], p, exp, m));
out
}
fn recombine(f: &[Int], lifted: &[Vec<Int>], m: &Int) -> Vec<Vec<Int>> {
let mut remaining = f.to_vec();
let mut pool: Vec<Vec<Int>> = lifted.to_vec();
let mut out = Vec::new();
let mut size = 1;
while size <= pool.len() {
let mut found = None;
for combo in subsets(pool.len(), size) {
let mut cand = vec![Int::ONE];
for &i in &combo {
cand = ip_sym_mod(&ip_mul(&cand, &pool[i]), m);
}
if (remaining.len() as isize - 1) < (cand.len() as isize - 1) {
continue;
}
let (q, r) = ip_divmod_monic(&remaining, &cand);
if r.is_empty() {
out.push(ip_primitive(&cand));
remaining = q;
found = Some(combo);
break;
}
}
match found {
Some(combo) => {
let mut idx = combo;
idx.sort_unstable_by(|a, b| b.cmp(a));
for i in idx {
pool.remove(i);
}
size = 1;
}
None => size += 1,
}
}
if (remaining.len() as isize - 1) > 0 {
out.push(ip_primitive(&remaining));
}
out
}
fn reduce_mod_p(f: &[Int], p: u64) -> Vec<u64> {
let pp = Int::from_u64(p);
trim(
f.iter()
.map(|c| c.rem_euclid(&pp).to_u64().unwrap_or(0))
.collect(),
)
}
fn factor_monic_squarefree(f: &[Int]) -> Vec<Vec<Int>> {
let n = f.len() - 1;
if n <= 1 {
return alloc::vec![f.to_vec()];
}
const PRIMES: [u64; 24] = [
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
];
let (p, fp) = PRIMES
.iter()
.find_map(|&p| {
let fp = reduce_mod_p(f, p);
if deg(&fp) != n as isize {
return None;
}
(deg(&fp_gcd(&fp, &fp_derivative(&fp, p), p)) == 0).then_some((p, fp))
})
.expect("a small prime keeps a square-free polynomial square-free mod p");
let modfacs = factor_mod_p(&fp, p);
let r = modfacs.len();
if r == 1 {
return alloc::vec![f.to_vec()]; }
let norm1 = f.iter().fold(Int::ZERO, |a, c| a.add(&c.abs()));
let mut bound = Int::ONE.mul_2k(n as u32 + 1).mul(&norm1);
#[cfg(feature = "lattice")]
{
let rb = root_bound(f);
let inner = Int::from_u64(2 * n as u64)
.mul(&rb.pow(r as u32))
.mul(&Int::ONE.mul_2k(2 * r as u32));
let sq = inner.mul(&inner);
let b_vh = sq.mul(&sq); if b_vh.cmp(&bound) == core::cmp::Ordering::Greater {
bound = b_vh;
}
}
let mut m = Int::from_u64(p);
let mut exp = 1u32;
while m.cmp(&bound) != core::cmp::Ordering::Greater {
m = m.mul(&Int::from_u64(p));
exp += 1;
}
let lifted = lift_all(f, &modfacs, p, exp, &m);
#[cfg(feature = "lattice")]
if let Some(factors) = van_hoeij(f, &lifted, &m) {
return factors;
}
recombine(f, &lifted, &m)
}
fn factor_primitive_squarefree(f: &[Int]) -> Vec<Vec<Int>> {
let n = f.len() - 1;
if n <= 1 {
return alloc::vec![f.to_vec()];
}
let l = f.last().unwrap().clone();
if l == Int::ONE {
return factor_monic_squarefree(f);
}
if l == Int::from_i64(-1) {
let neg: Vec<Int> = f.iter().map(Int::neg).collect();
return factor_monic_squarefree(&neg);
}
let mut bigf = alloc::vec![Int::ZERO; n + 1];
bigf[n] = Int::ONE;
for (k, slot) in bigf.iter_mut().enumerate().take(n) {
*slot = f[k].mul(&l.pow((n - 1 - k) as u32));
}
factor_monic_squarefree(&bigf)
.iter()
.map(|g| {
let sub: Vec<Int> = g
.iter()
.enumerate()
.map(|(i, c)| c.mul(&l.pow(i as u32)))
.collect();
ip_primitive(&ip_trim(sub))
})
.collect()
}
#[cfg(feature = "lattice")]
fn power_sums_mod(u: &[Int], l: usize, m: &Int) -> Vec<Int> {
let d = u.len() - 1;
let c = |i: usize| -> Int {
if (1..=d).contains(&i) {
u[d - i].rem_euclid(m)
} else {
Int::ZERO
}
};
let mut p: Vec<Int> = Vec::with_capacity(l);
for k in 1..=l {
let mut acc = Int::ZERO;
for i in 1..=(k - 1).min(d) {
acc = acc.add(&c(i).mul(&p[k - i - 1])).rem_euclid(m);
}
if k <= d {
acc = acc.add(&Int::from_u64(k as u64).mul(&c(k))).rem_euclid(m);
}
p.push(if acc.is_zero() {
Int::ZERO
} else {
m.sub(&acc)
}); }
p
}
#[cfg(feature = "lattice")]
fn root_bound(f: &[Int]) -> Int {
let mx = f[..f.len() - 1]
.iter()
.map(Int::abs)
.fold(Int::ZERO, |a, b| {
if a.cmp(&b) == core::cmp::Ordering::Less {
b
} else {
a
}
});
mx.add(&Int::ONE)
}
#[cfg(feature = "lattice")]
fn van_hoeij(f: &[Int], lifted: &[Vec<Int>], m: &Int) -> Option<Vec<Vec<Int>>> {
let r = lifted.len();
let m_t = r; let dim = r + m_t;
let traces: Vec<Vec<Int>> = lifted.iter().map(|u| power_sums_mod(u, m_t, m)).collect();
let mut basis: Vec<Vec<Int>> = Vec::with_capacity(dim);
for (i, tr) in traces.iter().enumerate() {
let mut row = alloc::vec![Int::ZERO; dim];
row[i] = Int::ONE;
for (k, t) in tr.iter().enumerate() {
row[r + k] = t.clone();
}
basis.push(row);
}
for k in 0..m_t {
let mut row = alloc::vec![Int::ZERO; dim];
row[r + k] = m.clone();
basis.push(row);
}
let reduced = crate::lattice::lll_reduce(&basis);
let tbits: Vec<u64> = reduced
.iter()
.map(|row| {
row[r..]
.iter()
.map(|e| u64::from(e.bit_len()))
.max()
.unwrap_or(0)
})
.collect();
let mut sorted = tbits.clone();
sorted.sort_unstable();
let gap_min = (m.bit_len() as u64 / 8).max(2);
let mut threshold = m.bit_len() as u64;
for w in sorted.windows(2) {
if w[1] - w[0] >= gap_min {
threshold = w[1];
break;
}
}
let sol: Vec<&Vec<Int>> = reduced
.iter()
.zip(&tbits)
.filter(|(_, tb)| **tb < threshold)
.map(|(row, _)| row)
.collect();
if sol.is_empty() {
return None;
}
let mut labels = alloc::vec![0usize; r];
let mut sigs: Vec<Vec<Int>> = Vec::new();
for (i, label) in labels.iter_mut().enumerate() {
let sig: Vec<Int> = sol.iter().map(|v| v[i].clone()).collect();
*label = match sigs.iter().position(|s| *s == sig) {
Some(idx) => idx,
None => {
sigs.push(sig);
sigs.len() - 1
}
};
}
let mut factors = Vec::with_capacity(sigs.len());
for g in 0..sigs.len() {
let mut cand = alloc::vec![Int::ONE];
for (i, &lab) in labels.iter().enumerate() {
if lab == g {
cand = ip_sym_mod(&ip_mul(&cand, &lifted[i]), m);
}
}
if (f.len() as isize) < (cand.len() as isize) || !ip_divmod_monic(f, &cand).1.is_empty() {
return None;
}
factors.push(ip_primitive(&cand));
}
Some(factors)
}
fn subsets(n: usize, k: usize) -> Vec<Vec<usize>> {
let mut out = Vec::new();
let mut combo: Vec<usize> = (0..k).collect();
if k == 0 || k > n {
return out;
}
loop {
out.push(combo.clone());
let mut i = k;
while i > 0 {
i -= 1;
if combo[i] != i + n - k {
combo[i] += 1;
for j in i + 1..k {
combo[j] = combo[j - 1] + 1;
}
break;
}
if i == 0 {
return out;
}
}
}
}
use crate::poly::Poly;
use crate::rational::Rational;
fn is_constant(p: &Poly<Rational>) -> bool {
p.degree().is_none_or(|d| d == 0)
}
fn yun(f: &Poly<Rational>) -> alloc::vec::Vec<(Poly<Rational>, usize)> {
let fp = f.derivative();
let a0 = f.gcd(&fp);
let mut b = f.div_rem(&a0).0;
let mut c = fp.div_rem(&a0).0;
let mut d = c.sub(&b.derivative());
let mut out = alloc::vec::Vec::new();
let mut i = 1;
while !is_constant(&b) {
let a = b.gcd(&d);
if !is_constant(&a) {
out.push((a.monic(), i));
}
let b_next = b.div_rem(&a).0;
c = d.div_rem(&a).0;
d = c.sub(&b_next.derivative());
b = b_next;
i += 1;
}
out
}
fn to_primitive_int(p: &Poly<Rational>) -> Vec<Int> {
let mut lcm = Int::ONE;
for c in p.coeffs() {
lcm = lcm.lcm(c.denominator());
}
let ints: Vec<Int> = p
.coeffs()
.iter()
.map(|c| c.numerator().mul(&lcm.div_exact(c.denominator())))
.collect();
ip_primitive(&ip_trim(ints))
}
fn to_monic_rat(g: &[Int]) -> Poly<Rational> {
let lc = g.last().unwrap();
Poly::new(
g.iter()
.map(|c| Rational::new(c.clone(), lc.clone()))
.collect(),
)
}
pub(crate) fn factor_rational(f: &Poly<Rational>) -> alloc::vec::Vec<(Poly<Rational>, usize)> {
if is_constant(f) {
return alloc::vec::Vec::new();
}
let mut out = alloc::vec::Vec::new();
for (sqfree, mult) in yun(&f.monic()) {
for g in factor_primitive_squarefree(&to_primitive_int(&sqfree)) {
out.push((to_monic_rat(&g), mult));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn prod(fs: &[Vec<u64>], p: u64) -> Vec<u64> {
fs.iter().fold(vec![1u64], |acc, g| fp_mul(&acc, g, p))
}
#[test]
fn cantor_zassenhaus_reconstructs() {
let p = 13;
let f = fp_mul(&fp_mul(&[1, 1], &[2, 1], p), &[2, 0, 1], p);
let fm = fp_monic(&f, p);
let facs = factor_mod_p(&fm, p);
assert_eq!(fp_monic(&prod(&facs, p), p), fm, "product mismatch");
assert_eq!(facs.len(), 3, "expected 3 irreducible factors: {facs:?}");
assert_eq!(facs.iter().filter(|g| deg(g) == 2).count(), 1);
}
#[test]
fn fully_split_linear() {
let p = 13;
let mut f = vec![1u64];
for r in 1..=5u64 {
f = fp_mul(&f, &[p - r, 1], p);
}
let facs = factor_mod_p(&fp_monic(&f, p), p);
assert_eq!(facs.len(), 5);
assert!(facs.iter().all(|g| deg(g) == 1));
assert_eq!(fp_monic(&prod(&facs, p), p), fp_monic(&f, p));
}
}
#[cfg(all(test, feature = "lattice"))]
mod vh_tests {
use super::*;
fn ints(c: &[i64]) -> Vec<Int> {
c.iter().map(|&x| Int::from_i64(x)).collect()
}
fn run_vh(f: &[Int]) -> Option<Vec<Vec<Int>>> {
let n = f.len() - 1;
for &p in &[
3u64, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
] {
let fp = reduce_mod_p(f, p);
if deg(&fp) != n as isize || deg(&fp_gcd(&fp, &fp_derivative(&fp, p), p)) != 0 {
continue;
}
let modfacs = factor_mod_p(&fp, p);
if modfacs.len() == 1 {
return Some(alloc::vec![f.to_vec()]);
}
let r = modfacs.len();
let rb = root_bound(f);
let inner = Int::from_u64(2 * n as u64)
.mul(&rb.pow(r as u32))
.mul(&Int::ONE.mul_2k(2 * r as u32));
let sq = inner.mul(&inner);
let bound = sq.mul(&sq);
let mut m = Int::from_u64(p);
let mut exp = 1u32;
while m.cmp(&bound) != core::cmp::Ordering::Greater {
m = m.mul(&Int::from_u64(p));
exp += 1;
}
let lifted = lift_all(f, &modfacs, p, exp, &m);
return (modfacs.len(), van_hoeij(f, &lifted, &m)).1;
}
None
}
fn prod(fs: &[Vec<Int>]) -> Vec<Int> {
fs.iter().fold(alloc::vec![Int::ONE], |a, g| ip_mul(&a, g))
}
#[test]
fn van_hoeij_irreducible_swinnerton_dyer() {
let f = ints(&[1, 0, -10, 0, 1]);
let res = run_vh(&f).expect("van Hoeij must resolve");
assert_eq!(res.len(), 1, "should be irreducible: {res:?}");
assert_eq!(res[0], f);
}
#[test]
fn van_hoeij_recombines_into_quadratics() {
let f = ints(&[6, 0, -5, 0, 1]);
let mut res = run_vh(&f).expect("van Hoeij must resolve");
assert_eq!(res.len(), 2, "expected two quadratic factors: {res:?}");
assert!(res.iter().all(|g| g.len() - 1 == 2));
res.sort_by(|a, b| a[0].cmp(&b[0]));
assert_eq!(prod(&res), f);
}
#[test]
fn van_hoeij_multiple_split_factors() {
let f = ip_mul(
&ip_mul(&ints(&[1, 0, 1]), &ints(&[-2, 0, 1])),
&ints(&[-3, 0, 1]),
);
let mut res = run_vh(&f).expect("van Hoeij must resolve");
assert_eq!(res.len(), 3, "expected three quadratic factors: {res:?}");
assert!(res.iter().all(|g| g.len() - 1 == 2));
res.sort_by(|a, b| a[0].cmp(&b[0]));
assert_eq!(prod(&res), f);
}
#[test]
fn van_hoeij_mixed_reducible() {
let f = ip_mul(
&ip_mul(&ip_mul(&ints(&[-1, 1]), &ints(&[2, 1])), &ints(&[-2, 0, 1])),
&ints(&[1, 1, 1]),
);
let res = run_vh(&f).expect("van Hoeij must resolve");
assert_eq!(res.len(), 4, "expected four factors: {res:?}");
assert_eq!(
prod(&{
let mut v = res.clone();
v.sort_by(|a, b| a.len().cmp(&b.len()).then(a[0].cmp(&b[0])));
v
}),
f
);
}
#[test]
fn van_hoeij_degree_8_field() {
let f = ints(&[576, 0, -960, 0, 352, 0, -40, 0, 1]);
let res = run_vh(&f).expect("van Hoeij must resolve degree 8");
assert_eq!(res.len(), 1, "should be irreducible: {res:?}");
}
}