use super::{NULL_VAR, Polynomial, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use num_rational::BigRational;
use num_traits::{One, Zero};
fn combined_var(a: Var, b: Var) -> Var {
match (a, b) {
(NULL_VAR, NULL_VAR) => NULL_VAR,
(NULL_VAR, v) | (v, NULL_VAR) => v,
(va, vb) => va.max(vb),
}
}
#[derive(Debug, Clone)]
pub struct GcdConfig {
pub use_modular: bool,
pub use_subresultant: bool,
pub modulus: u64,
pub max_degree: u32,
}
impl Default for GcdConfig {
fn default() -> Self {
Self {
use_modular: true,
use_subresultant: true,
modulus: 2147483647, max_degree: 1000,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GcdStats {
pub gcds_computed: u64,
pub euclidean_steps: u64,
pub modular_reductions: u64,
pub time_us: u64,
}
pub struct PolynomialGcd {
config: GcdConfig,
stats: GcdStats,
}
impl PolynomialGcd {
pub fn new() -> Self {
Self::with_config(GcdConfig::default())
}
pub fn with_config(config: GcdConfig) -> Self {
Self {
config,
stats: GcdStats::default(),
}
}
pub fn stats(&self) -> &GcdStats {
&self.stats
}
pub fn gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
#[cfg(feature = "std")]
let start = std::time::Instant::now();
if a.is_zero() {
self.stats.gcds_computed += 1;
return b.clone();
}
if b.is_zero() {
self.stats.gcds_computed += 1;
return a.clone();
}
if a.total_degree() > self.config.max_degree || b.total_degree() > self.config.max_degree {
self.stats.gcds_computed += 1;
return Polynomial::constant(BigRational::one());
}
let result = if a.total_degree() < 10 && b.total_degree() < 10 {
self.euclidean_gcd(a, b)
} else if self.config.use_modular {
self.modular_gcd(a, b)
} else {
self.euclidean_gcd(a, b)
};
self.stats.gcds_computed += 1;
#[cfg(feature = "std")]
{
self.stats.time_us += start.elapsed().as_micros() as u64;
}
result
}
fn euclidean_gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
let mut r0 = a.clone();
let mut r1 = b.clone();
let max_iters = a.total_degree() as usize + b.total_degree() as usize + 16;
let mut iters = 0;
while !r1.is_zero() && iters < max_iters {
iters += 1;
self.stats.euclidean_steps += 1;
let remainder = self.polynomial_remainder(&r0, &r1);
r0 = r1;
r1 = remainder;
}
self.normalize_polynomial(r0)
}
fn modular_gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
self.stats.modular_reductions += 1;
self.euclidean_gcd(a, b)
}
fn polynomial_remainder(&self, a: &Polynomial, b: &Polynomial) -> Polynomial {
if b.is_zero() {
return a.clone();
}
if a.is_zero() {
return Polynomial::zero();
}
let var = combined_var(a.max_var(), b.max_var());
if var == NULL_VAR {
return Polynomial::zero();
}
a.exact_div_rem_univariate(b, var).1
}
fn normalize_polynomial(&self, poly: Polynomial) -> Polynomial {
if poly.is_zero() {
return poly;
}
let leading_coeff = poly.leading_coeff();
if leading_coeff.is_one() || leading_coeff.is_zero() {
return poly;
}
poly.scale(&leading_coeff.recip())
}
pub fn gcd_multiple(&mut self, polys: &[Polynomial]) -> Polynomial {
if polys.is_empty() {
return Polynomial::zero();
}
let mut result = polys[0].clone();
for poly in &polys[1..] {
result = self.gcd(&result, poly);
if result.total_degree() == 0 && !result.is_zero() {
break;
}
}
result
}
pub fn lcm(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
if a.is_zero() || b.is_zero() {
return Polynomial::zero();
}
let gcd = self.gcd(a, b);
if gcd.is_zero() {
return Polynomial::zero();
}
let product = a.mul(b);
let var = combined_var(product.max_var(), gcd.max_var());
if var == NULL_VAR {
return Polynomial::one();
}
let (quotient, _remainder) = product.exact_div_rem_univariate(&gcd, var);
quotient
}
}
impl Default for PolynomialGcd {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use num_rational::BigRational;
#[test]
fn test_gcd_creation() {
let gcd_engine = PolynomialGcd::new();
assert_eq!(gcd_engine.stats().gcds_computed, 0);
}
#[test]
fn test_gcd_zero() {
let mut gcd_engine = PolynomialGcd::new();
let a = Polynomial::constant(BigRational::from(BigInt::from(42)));
let b = Polynomial::zero();
let result = gcd_engine.gcd(&a, &b);
assert!(!result.is_zero());
assert_eq!(gcd_engine.stats().gcds_computed, 1);
}
#[test]
fn test_gcd_constants() {
let mut gcd_engine = PolynomialGcd::new();
let a = Polynomial::constant(BigRational::from(BigInt::from(12)));
let b = Polynomial::constant(BigRational::from(BigInt::from(18)));
let result = gcd_engine.gcd(&a, &b);
assert!(!result.is_zero());
}
#[test]
fn test_gcd_config() {
let config = GcdConfig {
use_modular: false,
use_subresultant: false,
..Default::default()
};
let gcd_engine = PolynomialGcd::with_config(config);
assert!(!gcd_engine.config.use_modular);
}
#[test]
fn test_gcd_multiple() {
let mut gcd_engine = PolynomialGcd::new();
let polys = vec![
Polynomial::constant(BigRational::from(BigInt::from(12))),
Polynomial::constant(BigRational::from(BigInt::from(18))),
Polynomial::constant(BigRational::from(BigInt::from(24))),
];
let result = gcd_engine.gcd_multiple(&polys);
assert!(!result.is_zero());
}
#[test]
fn test_lcm() {
let mut gcd_engine = PolynomialGcd::new();
let a = Polynomial::constant(BigRational::from(BigInt::from(4)));
let b = Polynomial::constant(BigRational::from(BigInt::from(6)));
let result = gcd_engine.lcm(&a, &b);
assert!(!result.is_zero());
}
fn poly_x_squared() -> Polynomial {
Polynomial::from_coeffs_int(&[(1, &[(0, 2)])])
}
fn poly_x_plus_1() -> Polynomial {
Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])])
}
fn poly_x_minus_1() -> Polynomial {
Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])])
}
#[test]
fn test_polynomial_remainder_is_real_division_not_stub() {
let gcd_engine = PolynomialGcd::new();
let a = poly_x_squared();
let b = poly_x_plus_1();
let remainder = gcd_engine.polynomial_remainder(&a, &b);
assert!(!remainder.is_zero(), "stub would wrongly return zero here");
assert!(remainder.is_constant());
assert_eq!(remainder.constant_value(), BigRational::one());
}
#[test]
fn test_gcd_coprime_polynomials_is_a_unit() {
let mut gcd_engine = PolynomialGcd::new();
let a = poly_x_squared();
let b = poly_x_plus_1();
let result = gcd_engine.gcd(&a, &b);
assert!(!result.is_zero());
assert_eq!(
result.total_degree(),
0,
"gcd of coprime polynomials must be a nonzero constant, got {result:?}"
);
}
#[test]
fn test_gcd_shared_linear_factor() {
let mut gcd_engine = PolynomialGcd::new();
let a = poly_x_minus_1().mul(&poly_x_plus_1()); let b = poly_x_minus_1().mul(&Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[])]));
let result = gcd_engine.gcd(&a, &b);
assert_eq!(result.total_degree(), 1, "expected a linear common factor");
assert!(gcd_engine.polynomial_remainder(&a, &result).is_zero());
assert!(gcd_engine.polynomial_remainder(&b, &result).is_zero());
assert_eq!(result.leading_coeff(), BigRational::one());
}
#[test]
fn test_normalize_polynomial_makes_monic() {
let gcd_engine = PolynomialGcd::new();
let poly = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (4, &[])]);
let normalized = gcd_engine.normalize_polynomial(poly);
assert_eq!(normalized.leading_coeff(), BigRational::one());
assert_eq!(
normalized,
Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[])]) );
}
#[test]
fn test_normalize_polynomial_zero_and_already_monic_unchanged() {
let gcd_engine = PolynomialGcd::new();
let zero = Polynomial::zero();
assert!(gcd_engine.normalize_polynomial(zero).is_zero());
let monic = poly_x_plus_1();
assert_eq!(gcd_engine.normalize_polynomial(monic.clone()), monic);
}
#[test]
fn test_lcm_of_coprime_polynomials_equals_product() {
let mut gcd_engine = PolynomialGcd::new();
let a = poly_x_minus_1(); let b = poly_x_plus_1();
let result = gcd_engine.lcm(&a, &b);
assert_eq!(
result,
Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])])
); }
#[test]
fn test_lcm_divides_product_by_true_gcd() {
let mut gcd_engine = PolynomialGcd::new();
let a = poly_x_minus_1().mul(&poly_x_plus_1()); let b = poly_x_minus_1().mul(&Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[])]));
let lcm = gcd_engine.lcm(&a, &b);
let product = a.mul(&b);
assert_eq!(lcm.total_degree(), 3);
assert!(gcd_engine.polynomial_remainder(&product, &lcm).is_zero());
}
#[test]
fn test_polynomial_remainder_zero_divisor_no_panic() {
let gcd_engine = PolynomialGcd::new();
let a = poly_x_plus_1();
let zero = Polynomial::zero();
let remainder = gcd_engine.polynomial_remainder(&a, &zero);
assert_eq!(remainder, a);
}
#[test]
fn test_gcd_disjoint_variables_terminates() {
let mut gcd_engine = PolynomialGcd::new();
let a = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]); let b = Polynomial::from_coeffs_int(&[(1, &[(1, 1)]), (1, &[])]);
let _ = gcd_engine.gcd(&a, &b); }
#[test]
fn test_gcd_constant_dividend_nonconstant_divisor_remainder() {
let gcd_engine = PolynomialGcd::new();
let five = Polynomial::constant(BigRational::from(BigInt::from(5)));
let b = poly_x_plus_1();
let remainder = gcd_engine.polynomial_remainder(&five, &b);
assert_eq!(remainder, five);
}
}