use std::sync::{Mutex, OnceLock};
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use super::modpoly::{Fp64, PolyIn, RingOps};
use super::multipoly::Exps;
use crate::base::rng::SplitMix64;
pub(crate) fn int_gcd(a: &BigInt, b: &BigInt) -> BigInt {
crate::base::numeric::gcd_big(a, b)
}
pub(crate) fn content<'a>(xs: impl IntoIterator<Item = &'a BigInt>) -> BigInt {
let xs: Vec<&BigInt> = xs.into_iter().filter(|x| !x.is_zero()).collect();
let Some(start) = (0..xs.len()).min_by_key(|&i| xs[i].bits()) else {
return BigInt::zero();
};
let mut g = xs[start].abs();
for (i, x) in xs.iter().enumerate() {
if g.is_one() {
break;
}
if i != start {
g = int_gcd(&g, x);
}
}
g
}
pub(crate) fn ratio_reduced(n: BigInt, d: BigInt) -> num_rational::Ratio<BigInt> {
if d.is_zero() {
return num_rational::Ratio::new(n, d);
}
let g = int_gcd(&n, &d);
let (mut n, mut d) = if g.is_one() { (n, d) } else { (n / &g, d / &g) };
if d.is_negative() {
n = -n;
d = -d;
}
num_rational::Ratio::new_raw(n, d)
}
pub(crate) fn rat_add(
a: &num_rational::Ratio<BigInt>,
b: &num_rational::Ratio<BigInt>,
) -> num_rational::Ratio<BigInt> {
use num_rational::Ratio;
if a.is_zero() {
return b.clone();
}
if b.is_zero() {
return a.clone();
}
match (a.denom().is_one(), b.denom().is_one()) {
(true, true) => Ratio::from_integer(a.numer() + b.numer()),
(false, true) => Ratio::new_raw(a.numer() + b.numer() * a.denom(), a.denom().clone()),
(true, false) => Ratio::new_raw(b.numer() + a.numer() * b.denom(), b.denom().clone()),
(false, false) => {
let (u, u1, v, v1) = (a.numer(), a.denom(), b.numer(), b.denom());
let d1 = int_gcd(u1, v1);
if d1.is_one() {
return Ratio::new_raw(u * v1 + v * u1, u1 * v1);
}
let t = u * (v1 / &d1) + v * (u1 / &d1);
if t.is_zero() {
return Ratio::zero();
}
let d2 = int_gcd(&t, &d1);
if d2.is_one() {
Ratio::new_raw(t, (u1 / &d1) * v1)
} else {
Ratio::new_raw(t / &d2, (u1 / &d1) * (v1 / &d2))
}
}
}
}
pub(crate) fn rat_mul(
a: &num_rational::Ratio<BigInt>,
b: &num_rational::Ratio<BigInt>,
) -> num_rational::Ratio<BigInt> {
use num_rational::Ratio;
if a.is_zero() || b.is_zero() {
return Ratio::zero();
}
if a.denom().is_one() && b.denom().is_one() {
return Ratio::from_integer(a.numer() * b.numer());
}
let div = |x: &BigInt, g: &BigInt| if g.is_one() { x.clone() } else { x / g };
let g1 = int_gcd(a.numer(), b.denom());
let g2 = int_gcd(b.numer(), a.denom());
let n = div(a.numer(), &g1) * div(b.numer(), &g2);
let d = div(a.denom(), &g2) * div(b.denom(), &g1);
if d.is_negative() {
Ratio::new_raw(-n, -d)
} else {
Ratio::new_raw(n, d)
}
}
pub(crate) fn int_lcm(a: &BigInt, b: &BigInt) -> BigInt {
if a.is_zero() || b.is_zero() {
return BigInt::zero();
}
(a / int_gcd(a, b) * b).abs()
}
fn prime_cache() -> &'static Mutex<Vec<u64>> {
static CACHE: OnceLock<Mutex<Vec<u64>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(Vec::new()))
}
const FIRST_PRIMES: [u64; 8] = [
4_611_686_018_427_387_847,
4_611_686_018_427_387_817,
4_611_686_018_427_387_787,
4_611_686_018_427_387_761,
4_611_686_018_427_387_751,
4_611_686_018_427_387_737,
4_611_686_018_427_387_733,
4_611_686_018_427_387_709,
];
pub(crate) fn prime(i: usize) -> u64 {
if let Some(&p) = FIRST_PRIMES.get(i) {
return p;
}
let mut cache = match prime_cache().lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
let j = i - FIRST_PRIMES.len();
while cache.len() <= j {
let last = cache
.last()
.copied()
.unwrap_or(FIRST_PRIMES[FIRST_PRIMES.len() - 1]);
let mut n = last - 1;
if n.is_multiple_of(2) {
n -= 1;
}
while !is_prime_u64(n) {
n -= 2;
}
cache.push(n);
}
cache[j]
}
fn is_prime_u64(n: u64) -> bool {
if n < 2 {
return false;
}
for p in [2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] {
if n.is_multiple_of(p) {
return n == p;
}
}
let mul = |a: u64, b: u64| ((u128::from(a) * u128::from(b)) % u128::from(n)) as u64;
let pow = |mut b: u64, mut e: u64| {
let mut r = 1u64;
while e > 0 {
if e & 1 == 1 {
r = mul(r, b);
}
b = mul(b, b);
e >>= 1;
}
r
};
let s = (n - 1).trailing_zeros();
let d = (n - 1) >> s;
'bases: for a in [2u64, 325, 9375, 28178, 450_775, 9_780_504, 1_795_265_022] {
let a = a % n;
if a == 0 {
continue;
}
let mut x = pow(a, d);
if x == 1 || x == n - 1 {
continue;
}
for _ in 1..s {
x = mul(x, x);
if x == n - 1 {
continue 'bases;
}
}
return false;
}
true
}
pub(crate) fn residue(c: &BigInt, p: u64) -> u64 {
let r = (c.magnitude() % p).to_u64().unwrap_or(0);
if r != 0 && c.is_negative() { p - r } else { r }
}
fn trim(c: &mut Vec<BigInt>) {
while c.last().is_some_and(Zero::is_zero) {
c.pop();
}
}
pub(crate) fn zx_div_exact(f: &[BigInt], h: &[BigInt]) -> Option<Vec<BigInt>> {
let lc = h.last()?;
let m = h.len() - 1;
let mut r = f.to_vec();
trim(&mut r);
if r.is_empty() {
return Some(Vec::new());
}
if r.len() < h.len() {
return None;
}
if !h[0].is_zero() && !(&r[0] % &h[0]).is_zero() {
return None;
}
let mut q = vec![BigInt::zero(); r.len() - m];
while r.len() > m {
let k = r.len() - 1;
let (qk, rem) = r[k].div_rem(lc);
if !rem.is_zero() {
return None;
}
r.pop();
let s = k - m;
if !qk.is_zero() {
for (rj, hj) in r[s..].iter_mut().zip(&h[..m]) {
*rj -= &qk * hj;
}
}
q[s] = qk;
}
trim(&mut r);
r.is_empty().then(|| {
trim(&mut q);
q
})
}
fn gcd_image(f: &[BigInt], g: &[BigInt], p: u64) -> Option<PolyIn<Fp64>> {
let field = Fp64::new(p);
let fp = PolyIn::from_coeffs(field, f.iter().map(|c| residue(c, p)).collect());
let gp = PolyIn::from_coeffs(field, g.iter().map(|c| residue(c, p)).collect());
if fp.degree() != Some(f.len() - 1) && gp.degree() != Some(g.len() - 1) {
return None;
}
Some(fp.gcd(&gp))
}
fn primitive_positive(mut c: Vec<BigInt>) -> Vec<BigInt> {
trim(&mut c);
let g = content(&c);
if !g.is_zero() && !g.is_one() {
for x in &mut c {
*x /= &g;
}
}
if c.last().is_some_and(Signed::is_negative) {
for x in &mut c {
*x = -std::mem::take(x);
}
}
c
}
pub(crate) fn zx_gcd(f: &[BigInt], g: &[BigInt]) -> Option<Vec<BigInt>> {
let f = primitive_positive(f.to_vec());
let g = primitive_positive(g.to_vec());
if f.is_empty() || g.is_empty() {
return None;
}
if f.len() == 1 || g.len() == 1 {
return Some(vec![BigInt::one()]);
}
let lcf = f.last()?;
let lcg = g.last()?;
let gamma = int_gcd(lcf, lcg);
let norm_bits = |c: &[BigInt]| {
let lc_bits = c.last().map_or(0, BigInt::bits);
let max_bits = c.iter().map(BigInt::bits).max().unwrap_or(0);
let sqrt_len = (64 - (c.len() as u64).leading_zeros()) as u64;
max_bits + sqrt_len - lc_bits.min(max_bits)
};
let d_max = (f.len().min(g.len()) - 1) as u64;
let h_bits = gamma.bits() + d_max + norm_bits(&f).min(norm_bits(&g)) + 2;
let needed = (h_bits / 61 + 2) as usize;
let cap = 2 * needed + 64;
let mut bound = usize::MAX;
let mut hs: Vec<BigInt> = Vec::new();
let mut m = BigInt::one();
let mut tried = 0usize;
let mut i = 0usize;
while tried < cap {
let p = prime(i);
i += 1;
if residue(&gamma, p) == 0 {
continue;
}
tried += 1;
let Some(hp) = gcd_image(&f, &g, p) else {
continue;
};
let Some(deg) = hp.degree() else {
continue;
};
if deg == 0 {
return Some(vec![BigInt::one()]);
}
if deg > bound {
continue;
}
let field = *hp.ring();
let gp = residue(&gamma, p);
let img: Vec<u64> = hp.coeffs().iter().map(|&c| field.mul(&c, &gp)).collect();
if deg < bound {
bound = deg;
m = BigInt::from(p);
hs = img.iter().map(|&r| symmetric(r, p)).collect();
if let Some(h) = verified(&f, &g, &hs) {
return Some(h);
}
continue;
}
let m_inv = field.inv(&residue(&m, p)).unwrap_or(0);
let mp = &m * BigInt::from(p);
let half = &mp >> 1u32;
let mut stable = true;
for (h, &r) in hs.iter_mut().zip(&img) {
let delta = field.mul(&field.sub(&r, &residue(h, p)), &m_inv);
if delta != 0 {
stable = false;
*h += &m * BigInt::from(delta);
if *h > half {
*h -= ∓
}
}
}
m = mp;
if stable && let Some(h) = verified(&f, &g, &hs) {
return Some(h);
}
}
None
}
fn symmetric(r: u64, p: u64) -> BigInt {
if r > p / 2 {
BigInt::from(r) - BigInt::from(p)
} else {
BigInt::from(r)
}
}
fn verified(f: &[BigInt], g: &[BigInt], hs: &[BigInt]) -> Option<Vec<BigInt>> {
let h = primitive_positive(hs.to_vec());
if h.len() < 2 {
return None;
}
zx_div_exact(f, &h)?;
zx_div_exact(g, &h)?;
Some(h)
}
const CERTIFY_PRIMES: usize = 2;
const CERTIFY_POINTS: usize = 3;
pub(crate) fn coprime_certified<'a>(
nv: usize,
f: &[(&'a [u32], &'a BigInt)],
g: &[(&'a [u32], &'a BigInt)],
) -> bool {
let degs = |t: &[(&'a [u32], &'a BigInt)]| -> Vec<u32> {
(0..nv)
.map(|v| t.iter().map(|(e, _)| e[v]).max().unwrap_or(0))
.collect()
};
let (df, dg) = (degs(f), degs(g));
let common: Vec<usize> = (0..nv).filter(|&v| df[v] > 0 && dg[v] > 0).collect();
if common.is_empty() {
return true;
}
let mut rng = SplitMix64::new(0x5EED_6CD0_0000_0001);
'primes: for i in 0..CERTIFY_PRIMES {
let p = prime(i);
let field = Fp64::new(p);
let fr: Vec<u64> = f.iter().map(|(_, c)| residue(c, p)).collect();
let gr: Vec<u64> = g.iter().map(|(_, c)| residue(c, p)).collect();
'vars: for &v in &common {
for _ in 0..CERTIFY_POINTS {
let point: Vec<u64> = (0..nv).map(|_| 1 + rng.next_u64() % (p - 1)).collect();
let fi = univariate_image(field, f, &fr, v, &point, df[v]);
let gi = univariate_image(field, g, &gr, v, &point, dg[v]);
if fi.last() == Some(&0) && gi.last() == Some(&0) {
continue;
}
let fp = PolyIn::from_coeffs(field, fi);
let gp = PolyIn::from_coeffs(field, gi);
if fp.gcd(&gp).degree() == Some(0) {
continue 'vars;
}
return false;
}
continue 'primes;
}
return true;
}
false
}
fn univariate_image(
field: Fp64,
t: &[(&[u32], &BigInt)],
r: &[u64],
v: usize,
point: &[u64],
deg: u32,
) -> Vec<u64> {
let mut out = vec![0u64; deg as usize + 1];
for ((e, _), &c) in t.iter().zip(r) {
if c == 0 {
continue;
}
let mut val = c;
for (j, &ej) in e.iter().enumerate() {
if j != v && ej > 0 {
val = field.mul(&val, &field.pow(point[j], u64::from(ej)));
}
}
let slot = &mut out[e[v] as usize];
*slot = field.add(slot, &val);
}
out
}
pub(crate) fn main_gcd_degree_bound(
nv: usize,
f: &[(&[u32], &BigInt)],
g: &[(&[u32], &BigInt)],
) -> Option<usize> {
let deg0 = |t: &[(&[u32], &BigInt)]| t.iter().map(|(e, _)| e[0]).max();
let (df, dg) = (deg0(f)?, deg0(g)?);
if df == 0 || dg == 0 {
return Some(0);
}
let mut rng = SplitMix64::new(0x5EED_6CD0_0000_0002);
let mut bound: Option<usize> = None;
for i in 0..CERTIFY_PRIMES {
let p = prime(i);
let field = Fp64::new(p);
let fr: Vec<u64> = f.iter().map(|(_, c)| residue(c, p)).collect();
let gr: Vec<u64> = g.iter().map(|(_, c)| residue(c, p)).collect();
for _ in 0..CERTIFY_POINTS {
let point: Vec<u64> = (0..nv).map(|_| 1 + rng.next_u64() % (p - 1)).collect();
let fi = univariate_image(field, f, &fr, 0, &point, df);
if fi.last() == Some(&0) {
continue;
}
let gi = univariate_image(field, g, &gr, 0, &point, dg);
let fp = PolyIn::from_coeffs(field, fi);
let k = fp
.gcd(&PolyIn::from_coeffs(field, gi))
.degree()
.unwrap_or(0);
if k == 0 {
return Some(0);
}
bound = Some(bound.map_or(k, |b| b.min(k)));
break;
}
}
bound
}
pub(crate) type ZMap = std::collections::BTreeMap<Exps, BigInt>;
const HEU_GCD_MAX_TRIES: usize = 6;
#[derive(Debug, PartialEq)]
pub(crate) struct HeuGcd {
pub(crate) h: ZMap,
pub(crate) cff: ZMap,
pub(crate) cfg: ZMap,
}
enum Attempt {
Found(HeuGcd),
Retry,
Failed,
}
pub(crate) fn heu_gcd(nv: usize, f: &ZMap, g: &ZMap) -> Option<HeuGcd> {
let cf = content(f.values());
let cg = content(g.values());
if cf.is_zero() || cg.is_zero() {
return None;
}
let c = int_gcd(&cf, &cg);
let f = zmap_div_scalar(f, &c);
let g = zmap_div_scalar(g, &c);
let one = || ZMap::from([(Exps::from_elem(0, nv), BigInt::one())]);
let constant_of = |p: &ZMap| -> Option<BigInt> {
match p.len() {
1 => p.get(&Exps::from_elem(0, nv)).cloned(),
_ => None,
}
};
if nv == 0 || constant_of(&f).is_some() || constant_of(&g).is_some() {
let k = int_gcd(&content(f.values()), &content(g.values()));
if k.is_zero() {
return None;
}
let h = ZMap::from([(Exps::from_elem(0, nv), &k * &c)]);
return Some(HeuGcd {
h,
cff: zmap_div_scalar(&f, &k),
cfg: zmap_div_scalar(&g, &k),
});
}
if let Some(q) = zmap_div_exact(&g, &f) {
return Some(HeuGcd {
h: zmap_scale(&f, &c),
cff: one(),
cfg: q,
});
}
if let Some(q) = zmap_div_exact(&f, &g) {
return Some(HeuGcd {
h: zmap_scale(&g, &c),
cff: q,
cfg: one(),
});
}
let f_norm = max_norm(&f);
let g_norm = max_norm(&g);
let two = BigInt::from(2);
let b = &two * f_norm.clone().min(g_norm.clone()) + BigInt::from(29);
let lc_ratio = |norm: &BigInt, p: &ZMap| -> BigInt {
let lc = p
.last_key_value()
.map(|(_, c)| c.abs())
.filter(|c| !c.is_zero())
.unwrap_or_else(BigInt::one);
norm / lc
};
let mut xi = (b.clone().min(BigInt::from(99) * b.sqrt()))
.max(&two * lc_ratio(&f_norm, &f).min(lc_ratio(&g_norm, &g)) + BigInt::from(4));
for _ in 0..HEU_GCD_MAX_TRIES {
match heu_attempt(nv, &f, &g, &xi) {
Attempt::Found(found) => {
return Some(HeuGcd {
h: zmap_scale(&found.h, &c),
..found
});
}
Attempt::Retry => {}
Attempt::Failed => return None,
}
let root4 = xi.sqrt().sqrt().max(BigInt::from(2));
xi = (BigInt::from(73794) * &xi * root4) / BigInt::from(27011);
}
None
}
fn heu_attempt(nv: usize, f: &ZMap, g: &ZMap, xi: &BigInt) -> Attempt {
let ff = eval_last(nv, f, xi);
let gg = eval_last(nv, g, xi);
if ff.is_empty() || gg.is_empty() {
return Attempt::Retry;
}
let Some(HeuGcd { h, cff, cfg }) = heu_gcd(nv - 1, &ff, &gg) else {
return Attempt::Failed;
};
let h = interpolate(&h, xi);
let ch = content(h.values());
if !h.is_empty() && !ch.is_zero() {
let h = zmap_div_scalar(&h, &ch);
if let Some(cf) = zmap_div_exact(f, &h)
&& let Some(cg) = zmap_div_exact(g, &h)
{
return Attempt::Found(HeuGcd {
h,
cff: cf,
cfg: cg,
});
}
}
let cff = interpolate(&cff, xi);
if !cff.is_empty()
&& let Some(h) = zmap_div_exact(f, &cff)
&& let Some(cg) = zmap_div_exact(g, &h)
{
return Attempt::Found(HeuGcd { h, cff, cfg: cg });
}
let cfg = interpolate(&cfg, xi);
if !cfg.is_empty()
&& let Some(h) = zmap_div_exact(g, &cfg)
&& let Some(cf) = zmap_div_exact(f, &h)
{
return Attempt::Found(HeuGcd { h, cff: cf, cfg });
}
Attempt::Retry
}
fn eval_last(nv: usize, p: &ZMap, xi: &BigInt) -> ZMap {
let top = p.keys().map(|e| e[nv - 1]).max().unwrap_or(0) as usize;
let mut pows = Vec::with_capacity(top + 1);
let mut acc = BigInt::one();
for _ in 0..=top {
pows.push(acc.clone());
acc *= xi;
}
let mut out = ZMap::new();
for (e, c) in p {
let term = c * &pows[e[nv - 1] as usize];
*out.entry(Exps::from_slice(&e[..nv - 1])).or_default() += term;
}
out.retain(|_, c| !c.is_zero());
out
}
fn interpolate(h: &ZMap, xi: &BigInt) -> ZMap {
let mut out = ZMap::new();
for (e, c) in h {
let mut rest = c.clone();
let mut i = 0u32;
while !rest.is_zero() {
let mut d = rest.mod_floor(xi);
if &d + &d > *xi {
d -= xi;
}
rest -= &d;
rest /= xi;
if !d.is_zero() {
let mut k = Exps::with_capacity(e.len() + 1);
k.extend_from_slice(e);
k.push(i);
out.insert(k, d);
}
i += 1;
}
}
out
}
fn max_norm(p: &ZMap) -> BigInt {
p.values().map(Signed::abs).max().unwrap_or_default()
}
fn zmap_scale(p: &ZMap, c: &BigInt) -> ZMap {
if c.is_one() {
return p.clone();
}
p.iter().map(|(e, x)| (e.clone(), x * c)).collect()
}
fn zmap_div_scalar(p: &ZMap, c: &BigInt) -> ZMap {
if c.is_one() {
return p.clone();
}
p.iter().map(|(e, x)| (e.clone(), x / c)).collect()
}
pub(crate) fn zmap_div_exact(f: &ZMap, h: &ZMap) -> Option<ZMap> {
let (he, hc) = h.last_key_value()?;
let Some((fe0, fc0)) = f.first_key_value() else {
return Some(ZMap::new());
};
let (he0, hc0) = h.first_key_value()?;
if fe0.iter().zip(he0).any(|(a, b)| a < b) || !(fc0 % hc0).is_zero() {
return None;
}
let nv = he.len();
let deg = |p: &ZMap| -> Vec<u32> {
(0..nv)
.map(|v| p.keys().map(|e| e[v]).max().unwrap_or(0))
.collect()
};
let (df, dh) = (deg(f), deg(h));
if df.iter().zip(&dh).any(|(a, b)| a < b) {
return None;
}
let mut r = f.clone();
let mut q = ZMap::new();
while let Some((e, c)) = r.last_key_value() {
if e.iter().zip(he).any(|(a, b)| a < b) {
return None;
}
let qe: Exps = e.iter().zip(he).map(|(a, b)| a - b).collect();
let (qc, rem) = c.div_rem(hc);
if !rem.is_zero() {
return None;
}
for (e2, c2) in h {
let k: Exps = e2.iter().zip(&qe).map(|(a, b)| a + b).collect();
if k.iter().zip(&df).any(|(a, b)| a > b) {
return None;
}
let t = &qc * c2;
match r.entry(k) {
std::collections::btree_map::Entry::Occupied(mut slot) => {
*slot.get_mut() -= t;
if slot.get().is_zero() {
slot.remove();
}
}
std::collections::btree_map::Entry::Vacant(slot) => {
slot.insert(-t);
}
}
}
q.insert(qe, qc);
}
Some(q)
}
#[cfg(test)]
mod tests {
use super::*;
fn b(s: &str) -> BigInt {
s.parse().unwrap()
}
#[test]
fn lehmer_matches_binary_gcd() {
let mut rng = SplitMix64::new(7);
let mut big = |words: usize| -> BigInt {
let mut x = BigInt::zero();
for _ in 0..words {
x = (x << 64u32) + BigInt::from(rng.next_u64());
}
x
};
for (wa, wb, wg) in [
(1, 1, 0),
(3, 2, 1),
(5, 5, 2),
(40, 40, 7),
(60, 3, 1),
(2, 70, 30),
] {
let g: BigInt = big(wg) + 1;
let a: BigInt = big(wa) * &g;
let bb: BigInt = -(big(wb) * &g);
assert_eq!(int_gcd(&a, &bb), a.gcd(&bb), "{wa} {wb} {wg}");
}
assert_eq!(int_gcd(&b("0"), &b("-12")), b("12"));
assert_eq!(int_gcd(&b("0"), &b("0")), b("0"));
let f = b("2").pow(4000u32) - 1;
let g = b("2").pow(2600u32) - 1;
assert_eq!(int_gcd(&f, &g), b("2").pow(200u32) - 1);
let (mut x, mut y) = (b("1"), b("1"));
for _ in 0..3000 {
(x, y) = (y.clone(), x + y);
}
assert_eq!(int_gcd(&x, &y), b("1"));
}
#[test]
fn rational_arithmetic_matches_ratio() {
use num_rational::Ratio;
let mut rng = SplitMix64::new(11);
let mut num = |words: usize| -> BigInt {
let mut x = BigInt::from(rng.next_u64() % 1000);
for _ in 0..words {
x = (x << 64u32) + BigInt::from(rng.next_u64());
}
if rng.next_u64().is_multiple_of(2) {
-x
} else {
x
}
};
let mut vals: Vec<Ratio<BigInt>> = Vec::new();
for w in [0usize, 1, 3, 9] {
let common = num(w).abs() + 1;
vals.push(Ratio::from_integer(num(w)));
vals.push(Ratio::new(num(w) * &common, num(w).abs() + 1));
vals.push(Ratio::new(num(w), (num(w).abs() + 1) * &common));
}
vals.push(Ratio::zero());
for a in &vals {
for b in &vals {
let (s, p) = (rat_add(a, b), rat_mul(a, b));
let (s0, p0) = (a + b, a * b);
assert_eq!(
(s.numer(), s.denom()),
(s0.numer(), s0.denom()),
"{a} + {b}"
);
assert_eq!(
(p.numer(), p.denom()),
(p0.numer(), p0.denom()),
"{a} * {b}"
);
}
}
assert_eq!(
ratio_reduced(BigInt::from(6), BigInt::from(-4)),
Ratio::new(BigInt::from(-3), BigInt::from(2))
);
assert_eq!(
int_lcm(&BigInt::from(-6), &BigInt::from(4)),
BigInt::from(12)
);
}
#[test]
fn content_and_primes() {
assert_eq!(content(&[b("12"), b("-18"), b("0"), b("30")]), b("6"));
assert_eq!(content(&[]), b("0"));
assert_eq!(content(&[b("0")]), b("0"));
assert_eq!(prime(0), (1u64 << 62) - 57);
for i in 0..12 {
assert!(is_prime_u64(prime(i)), "{i}");
assert!(prime(i + 1) < prime(i));
assert!(
(prime(i + 1) + 1..prime(i))
.step_by(2)
.all(|n| !is_prime_u64(n))
);
}
assert_eq!(crate::base::numeric::gcd_u64(0, 12), 12);
assert_eq!(crate::base::numeric::gcd_u64(48, 180), 12);
assert_eq!(
int_gcd(
&b("-340282366920938463463374607431768211455"),
&b("18446744073709551615")
),
b("18446744073709551615")
);
assert!(!is_prime_u64(3_215_031_751) && is_prime_u64(2_305_843_009_213_693_951));
}
fn zx(cs: &[i64]) -> Vec<BigInt> {
cs.iter().map(|&c| BigInt::from(c)).collect()
}
#[test]
fn modular_gcd_small() {
let f = zx(&[-3, 1, 2]);
let g = zx(&[-5, 4, -4, 4, 1]);
assert_eq!(zx_gcd(&f, &g), Some(zx(&[-1, 1])));
assert_eq!(zx_gcd(&zx(&[1, 1]), &zx(&[2, 1])), Some(zx(&[1])));
assert_eq!(zx_gcd(&zx(&[-6, 0, 6]), &zx(&[-4, -4])), Some(zx(&[1, 1])));
assert_eq!(
zx_div_exact(&zx(&[-1, 0, 1]), &zx(&[1, 1])),
Some(zx(&[-1, 1]))
);
assert_eq!(zx_div_exact(&zx(&[-1, 0, 1]), &zx(&[1, 2])), None);
}
#[test]
fn modular_gcd_huge_coefficients() {
let a = b("3").pow(3000u32) + 7;
let c = b("5").pow(2000u32) - 3;
let d = b("7").pow(1500u32) + 1;
let lin = |k: &BigInt, l: &BigInt| vec![k.clone(), l.clone()];
let mul = |p: &[BigInt], q: &[BigInt]| {
let mut out = vec![BigInt::zero(); p.len() + q.len() - 1];
for (i, x) in p.iter().enumerate() {
for (j, y) in q.iter().enumerate() {
out[i + j] += x * y;
}
}
out
};
let h = mul(&lin(&a, &b("1")), &lin(&-&d, &c));
let f = mul(&h, &zx(&[1, 1]));
let g = mul(&mul(&h, &zx(&[-1, 1])), &zx(&[-1, 1]));
assert_eq!(zx_gcd(&f, &g), Some(primitive_positive(h)));
}
fn zmap_univariate(cs: &[i64]) -> ZMap {
cs.iter()
.enumerate()
.filter(|(_, c)| **c != 0)
.map(|(i, &c)| (Exps::from_slice(&[i as u32]), BigInt::from(c)))
.collect()
}
#[test]
fn heuristic_retry() {
let h = zmap_univariate(&[1, 8, 28, 56, 70, 56, 28, 8, 1]);
let cf = zmap_univariate(&[-1, 1]);
let cg = zmap_univariate(&[1, 0, 1]);
let mul = |a: &ZMap, b: &ZMap| -> ZMap {
let mut out = ZMap::new();
for (ea, ca) in a {
for (eb, cb) in b {
*out.entry(Exps::from_slice(&[ea[0] + eb[0]])).or_default() += ca * cb;
}
}
out.retain(|_, c| !c.is_zero());
out
};
let f = mul(&h, &cf);
let g = mul(&h, &cg);
assert_eq!(max_norm(&f), BigInt::from(28));
assert_eq!(max_norm(&g), BigInt::from(112));
assert!(matches!(
heu_attempt(1, &f, &g, &BigInt::from(85)),
Attempt::Retry
));
match heu_attempt(1, &f, &g, &BigInt::from(696)) {
Attempt::Found(found) => {
let expected = HeuGcd {
h: h.clone(),
cff: cf.clone(),
cfg: cg.clone(),
};
assert_eq!(found, expected);
}
_ => panic!("ξ = 696 should recover the GCD"),
}
assert_eq!(heu_gcd(1, &f, &g).unwrap().h, h);
assert_eq!(
zmap_div_exact(&f, &h),
Some(mul(&cf, &zmap_univariate(&[1])))
);
assert_eq!(zmap_div_exact(&f, &cg), None);
}
#[test]
fn multivariate_certificate() {
let e = |a: u32, b: u32| vec![a, b];
let one = b("1");
let m1 = b("-1");
let (e11, e00, e10, e01) = (e(1, 1), e(0, 0), e(1, 0), e(0, 1));
let f = [(&e11[..], &one), (&e00[..], &one)];
let g = [(&e10[..], &one), (&e01[..], &one)];
assert!(coprime_certified(2, &f, &g));
let (e20, e02) = (e(2, 0), e(0, 2));
let g2 = [(&e20[..], &one), (&e02[..], &m1)];
assert!(coprime_certified(2, &f, &g2));
assert!(!coprime_certified(2, &g, &g2));
let fx = [(&e10[..], &one), (&e00[..], &one)];
let gy = [(&e01[..], &one), (&e00[..], &one)];
assert!(coprime_certified(2, &fx, &gy));
}
#[test]
fn main_variable_gcd_degree() {
let e = |a: u32, b: u32| vec![a, b];
let (one, m1, two) = (b("1"), b("-1"), b("2"));
let (e11, e00, e10, e01, e20, e02) = (e(1, 1), e(0, 0), e(1, 0), e(0, 1), e(2, 0), e(0, 2));
let f = [(&e11[..], &one), (&e00[..], &one)];
let g = [(&e10[..], &one), (&e01[..], &one)];
let g2 = [(&e20[..], &one), (&e02[..], &m1)];
assert_eq!(main_gcd_degree_bound(2, &f, &g), Some(0));
assert_eq!(main_gcd_degree_bound(2, &g, &g2), Some(1));
let a = [(&e11[..], &one), (&e01[..], &one)];
let c = [(&e11[..], &one), (&e01[..], &two)];
assert_eq!(main_gcd_degree_bound(2, &a, &c), Some(0));
assert!(!coprime_certified(2, &a, &c));
let p8: Vec<(Vec<u32>, BigInt)> = (0..=8u32)
.map(|k| {
(
e(k, 8 - k),
num_integer::binomial(BigInt::from(8), BigInt::from(k)),
)
})
.collect();
let d8: Vec<(Vec<u32>, BigInt)> = (1..=8u32)
.map(|k| (e(k - 1, 8 - k), BigInt::from(k) * &p8[k as usize].1))
.collect();
let tp: Vec<(&[u32], &BigInt)> = p8.iter().map(|(e, c)| (&e[..], c)).collect();
let td: Vec<(&[u32], &BigInt)> = d8.iter().map(|(e, c)| (&e[..], c)).collect();
assert_eq!(main_gcd_degree_bound(2, &tp, &td), Some(7));
let fy = [(&e01[..], &one), (&e00[..], &one)];
assert_eq!(main_gcd_degree_bound(2, &fy, &g), Some(0));
}
}