#![allow(
clippy::multiple_crate_versions,
clippy::redundant_pub_crate,
clippy::cast_possible_truncation,
clippy::missing_panics_doc,
clippy::many_single_char_names,
clippy::similar_names,
clippy::module_name_repetitions
)]
mod frac;
mod gcd;
mod hu_monagan;
mod huang_gao;
mod huang_monagan;
mod images;
mod linzip;
mod modular_division;
mod pgcd;
mod poly;
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::One;
use poly::IntPoly;
use polycore::Monomial;
pub use frac::Frac;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GcdAlgorithm {
#[default]
HuangGao,
HuangMonagan,
HuMonagan,
HuMonaganBivariate,
Zippel,
}
type Poly = polycore::Poly<BigRational>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GcdReport {
pub attempted: Vec<GcdAlgorithm>,
}
#[must_use]
pub fn gcd_with_report(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> (Poly, GcdReport) {
assert_eq!(f.nvars, g.nvars, "polynomials must share a ring");
let mut report = GcdReport {
attempted: Vec::new(),
};
let h = gcd::gcd_z_observed(&integral(f).0, &integral(g).0, algorithm, |a| {
report.attempted.push(a);
});
(normalized(&h, f), report)
}
fn integral(f: &Poly) -> (IntPoly, BigInt) {
let d = f
.terms
.iter()
.fold(BigInt::one(), |d, (_, c)| d.lcm(c.denom()));
let terms = f
.terms
.iter()
.map(|(m, c)| (m.exps().to_vec(), c.numer() * (&d / c.denom())));
(IntPoly::new(f.nvars, terms), d)
}
fn rational(h: &IntPoly, d: &BigInt, like: &Poly) -> Poly {
let terms = h.terms.iter().map(|(e, c)| {
(
Monomial::new(e.clone()),
BigRational::new(c.clone(), d.clone()),
)
});
Poly::new(terms.collect(), like.nvars, like.order.clone())
}
fn normalized(h: &IntPoly, like: &Poly) -> Poly {
rational(h, &BigInt::one(), like).primitive()
}
fn gcd_int(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> IntPoly {
assert_eq!(f.nvars, g.nvars, "polynomials must share a ring");
gcd::gcd_z(&integral(f).0, &integral(g).0, algorithm)
}
#[must_use]
pub fn gcd(f: &Poly, g: &Poly) -> Poly {
gcd_with_algorithm(f, g, GcdAlgorithm::default())
}
fn recover(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> Option<Poly> {
assert_eq!(f.nvars, g.nvars, "polynomials must share a ring");
gcd::try_gcd_z(&integral(f).0, &integral(g).0, algorithm).map(|h| normalized(&h, f))
}
#[must_use]
pub fn zippel(f: &Poly, g: &Poly) -> Poly {
recover(f, g, GcdAlgorithm::Zippel).expect("Zippel retries until reconstruction succeeds")
}
#[must_use]
pub fn huang_gao(f: &Poly, g: &Poly) -> Option<Poly> {
recover(f, g, GcdAlgorithm::HuangGao)
}
#[must_use]
pub fn huang_monagan(f: &Poly, g: &Poly) -> Option<Poly> {
recover(f, g, GcdAlgorithm::HuangMonagan)
}
#[must_use]
pub fn hu_monagan(f: &Poly, g: &Poly) -> Option<Poly> {
recover(f, g, GcdAlgorithm::HuMonagan)
}
#[must_use]
pub fn hu_monagan_bivariate(f: &Poly, g: &Poly) -> Option<Poly> {
recover(f, g, GcdAlgorithm::HuMonaganBivariate)
}
#[must_use]
pub fn cofactors(f: &Poly, g: &Poly) -> (Poly, Poly, Poly) {
cofactors_with_algorithm(f, g, GcdAlgorithm::default())
}
#[must_use]
pub fn gcd_with_algorithm(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> Poly {
normalized(&gcd_int(f, g, algorithm), f)
}
#[must_use]
pub fn cofactors_with_algorithm(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> (Poly, Poly, Poly) {
let h = gcd_int(f, g, algorithm);
let zero = Poly::zero(f.nvars, f.order.clone());
if h.is_zero() {
return (zero.clone(), zero.clone(), zero);
}
let h = normalized(&h, f);
let hi = integral(&h).0;
let (hf, df) = integral(f);
let (hg, dg) = integral(g);
let qf = hf.div_exact(&hi).expect("gcd divides f");
let qg = hg.div_exact(&hi).expect("gcd divides g");
(h, rational(&qf, &df, f), rational(&qg, &dg, g))
}
#[must_use]
pub fn lcm(f: &Poly, g: &Poly) -> Poly {
lcm_with_algorithm(f, g, GcdAlgorithm::default())
}
#[must_use]
pub fn lcm_with_algorithm(f: &Poly, g: &Poly, algorithm: GcdAlgorithm) -> Poly {
let h = gcd_int(f, g, algorithm);
if f.is_zero() || g.is_zero() {
return Poly::zero(f.nvars, f.order.clone());
}
let (hg, _) = integral(g);
normalized(
&integral(f).0.mul(&hg.div_exact(&h).expect("gcd divides g")),
f,
)
}