use rug::{Integer, Rational};
fn trimmed(p: &[Rational]) -> &[Rational] {
let n = p.iter().rposition(|c| *c != 0).map_or(0, |i| i + 1);
&p[..n]
}
fn monic(p: &[Rational]) -> Vec<Rational> {
match p.last() {
None => Vec::new(),
Some(lc) if *lc == 1 => p.to_vec(),
Some(lc) => {
let inv = lc.clone().recip();
p.iter().map(|c| Rational::from(c * &inv)).collect()
}
}
}
fn to_fmpz(p: &[Rational]) -> super::FlintPoly {
let mut l = Integer::from(1);
for c in p.iter().filter(|c| **c != 0) {
l.lcm_mut(c.denom());
}
let ints: Vec<Integer> = p
.iter()
.map(|c| c.numer() * Integer::from(&l / c.denom()))
.collect();
super::FlintPoly::from_rug_coefficients(&ints)
}
pub(crate) fn qpoly_gcd_monic(a: &[Rational], b: &[Rational]) -> Vec<Rational> {
let a = trimmed(a);
let b = trimmed(b);
if a.is_empty() {
return monic(b);
}
if b.is_empty() {
return monic(a);
}
if a.len() == 1 || b.len() == 1 {
return vec![Rational::from(1)];
}
let g = to_fmpz(a).gcd(&to_fmpz(b));
let coeffs: Vec<Rational> = (0..g.length())
.map(|i| Rational::from(g.get_coeff_flint(i).to_rug()))
.collect();
monic(trimmed(&coeffs))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn euclid(a: &[Rational], b: &[Rational]) -> Vec<Rational> {
let mut a = trimmed(a).to_vec();
let mut b = trimmed(b).to_vec();
while !b.is_empty() {
let lb = b.last().unwrap().clone();
while a.len() >= b.len() && !a.is_empty() {
let t = Rational::from(a.last().unwrap() / &lb);
let s = a.len() - b.len();
for (j, bj) in b.iter().enumerate() {
a[s + j] -= Rational::from(&t * bj);
}
let n = trimmed(&a).len();
a.truncate(n);
}
std::mem::swap(&mut a, &mut b);
}
monic(&a)
}
fn q(v: &[(i64, i64)]) -> Vec<Rational> {
v.iter().map(|&(n, d)| Rational::from((n, d))).collect()
}
fn mul(a: &[Rational], b: &[Rational]) -> Vec<Rational> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut out = vec![Rational::new(); a.len() + b.len() - 1];
for (i, x) in a.iter().enumerate() {
for (j, y) in b.iter().enumerate() {
out[i + j] += Rational::from(x * y);
}
}
out
}
#[test]
fn edge_cases() {
let z: Vec<Rational> = Vec::new();
let zz = q(&[(0, 1), (0, 1)]);
assert!(qpoly_gcd_monic(&z, &z).is_empty());
assert!(qpoly_gcd_monic(&zz, &z).is_empty());
let p = q(&[(1, 2), (3, 1), (-4, 3)]);
assert_eq!(qpoly_gcd_monic(&z, &p), monic(&p));
assert_eq!(qpoly_gcd_monic(&p, &zz), monic(&p));
assert_eq!(qpoly_gcd_monic(&q(&[(-5, 7)]), &p), q(&[(1, 1)]));
assert_eq!(qpoly_gcd_monic(&q(&[(-5, 7)]), &z), q(&[(1, 1)]));
let a = mul(&q(&[(-1, 2), (1, 1)]), &q(&[(3, 1), (1, 1)]));
let b = mul(&q(&[(-1, 2), (1, 1)]), &q(&[(-7, 1), (2, 1)]));
assert_eq!(qpoly_gcd_monic(&a, &b), q(&[(-1, 2), (1, 1)]));
}
fn arb_qpoly(max_len: usize) -> impl Strategy<Value = Vec<Rational>> {
prop::collection::vec((-50i64..50, 1i64..12), 0..max_len)
.prop_map(|v| v.into_iter().map(|(n, d)| Rational::from((n, d))).collect())
}
proptest! {
#[test]
fn matches_euclid(a in arb_qpoly(7), b in arb_qpoly(7), c in arb_qpoly(5)) {
let ac = mul(&a, &c);
let bc = mul(&b, &c);
prop_assert_eq!(qpoly_gcd_monic(&ac, &bc), euclid(&ac, &bc));
prop_assert_eq!(qpoly_gcd_monic(&a, &b), euclid(&a, &b));
}
}
#[test]
#[ignore]
fn qgcd_timing() {
let _ = qpoly_gcd_monic(
&[Rational::from(1), Rational::from(1)],
&[Rational::from(2), Rational::from(1)],
);
for &d in &[4usize, 8, 20, 40, 80] {
let gen = |seed: i64, len: usize| -> Vec<Rational> {
(0..len)
.map(|k| {
let v = ((k as i64 * 7919 + seed) % 41) - 20;
Rational::from((if v == 0 { 1 } else { v }, (k as i64 % 5) + 1))
})
.collect()
};
let c = gen(3, d / 2 + 1);
let a = mul(&gen(11, d / 2 + 1), &c);
let b = mul(&gen(17, d / 2 + 1), &c);
let reps = if d <= 8 { 2000 } else { 5 };
let t = std::time::Instant::now();
let mut x = Vec::new();
for _ in 0..reps {
x = euclid(&a, &b);
}
let old = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
let t = std::time::Instant::now();
let mut y = Vec::new();
for _ in 0..reps {
y = qpoly_gcd_monic(&a, &b);
}
let new = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
assert_eq!(x, y);
println!(
"gcd deg {}: euclid {old:.3} ms, flint {new:.3} ms",
a.len() - 1
);
}
}
}