#![allow(missing_docs)]
#[allow(unused_imports)]
use crate::prelude::*;
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Zero};
pub type Monomial = FxHashMap<usize, usize>;
#[derive(Debug, Clone)]
pub struct Term {
pub coefficient: BigRational,
pub monomial: Monomial,
}
#[derive(Debug, Clone)]
pub struct MultivariatePolynomial {
pub terms: Vec<Term>,
pub num_vars: usize,
}
#[derive(Debug, Clone, Default)]
pub struct GcdStats {
pub gcd_computations: u64,
pub subresultant_steps: u64,
pub modular_reductions: u64,
pub crt_reconstructions: u64,
pub heuristic_attempts: u64,
pub heuristic_successes: u64,
}
#[derive(Debug, Clone)]
pub struct GcdConfig {
pub use_heuristic: bool,
pub use_modular: bool,
pub sparse_threshold: f64,
}
impl Default for GcdConfig {
fn default() -> Self {
Self {
use_heuristic: true,
use_modular: true,
sparse_threshold: 0.5,
}
}
}
pub struct MultivariateGcdComputer {
config: GcdConfig,
stats: GcdStats,
}
impl MultivariateGcdComputer {
pub fn new(config: GcdConfig) -> Self {
Self {
config,
stats: GcdStats::default(),
}
}
pub fn gcd(
&mut self,
f: &MultivariatePolynomial,
g: &MultivariatePolynomial,
) -> Result<MultivariatePolynomial, String> {
self.stats.gcd_computations += 1;
if f.is_zero() {
return Ok(g.clone());
}
if g.is_zero() {
return Ok(f.clone());
}
let is_sparse = self.is_sparse(f) && self.is_sparse(g);
if self.config.use_heuristic && is_sparse {
self.stats.heuristic_attempts += 1;
if let Ok(gcd) = self.heuristic_gcd(f, g) {
self.stats.heuristic_successes += 1;
return Ok(gcd);
}
}
if self.config.use_modular {
return self.modular_gcd(f, g);
}
self.subresultant_gcd(f, g)
}
fn is_sparse(&self, poly: &MultivariatePolynomial) -> bool {
if poly.terms.is_empty() {
return true;
}
let max_degree = poly.total_degree();
let max_terms = (max_degree + 1).pow(poly.num_vars as u32);
let density = poly.terms.len() as f64 / max_terms as f64;
density < self.config.sparse_threshold
}
fn heuristic_gcd(
&mut self,
f: &MultivariatePolynomial,
g: &MultivariatePolynomial,
) -> Result<MultivariatePolynomial, String> {
let evaluation_points = vec![0i64, 1, -1, 2, -2];
let mut gcd_evaluations = Vec::new();
for &point in &evaluation_points {
let f_eval = self.evaluate_at_point(f, point);
let g_eval = self.evaluate_at_point(g, point);
let gcd_eval = self.univariate_gcd(&f_eval, &g_eval)?;
gcd_evaluations.push(gcd_eval);
}
let gcd_degree = gcd_evaluations[0].len();
if gcd_evaluations.iter().all(|g| g.len() == gcd_degree) {
self.interpolate_gcd(&gcd_evaluations, &evaluation_points)
} else {
Err("Heuristic GCD failed: inconsistent degrees".to_string())
}
}
fn evaluate_at_point(&self, _poly: &MultivariatePolynomial, _point: i64) -> Vec<BigRational> {
vec![BigRational::one()]
}
fn univariate_gcd(
&self,
f: &[BigRational],
g: &[BigRational],
) -> Result<Vec<BigRational>, String> {
let mut a = f.to_vec();
let mut b = g.to_vec();
while !Self::is_zero_poly(&b) {
let r = Self::poly_rem(&a, &b)?;
a = b;
b = r;
}
Ok(Self::make_monic(a))
}
fn is_zero_poly(poly: &[BigRational]) -> bool {
poly.is_empty() || poly.iter().all(|c| c.is_zero())
}
fn poly_rem(
dividend: &[BigRational],
divisor: &[BigRational],
) -> Result<Vec<BigRational>, String> {
if Self::is_zero_poly(divisor) {
return Err("Division by zero polynomial".to_string());
}
let mut remainder = dividend.to_vec();
let lc_divisor = &divisor[0];
while !Self::is_zero_poly(&remainder) && remainder.len() >= divisor.len() {
let lc_remainder = &remainder[0];
let quotient_coeff = lc_remainder / lc_divisor;
for i in 0..divisor.len() {
remainder[i] = &remainder[i] - "ient_coeff * &divisor[i];
}
while !remainder.is_empty() && remainder[0].is_zero() {
remainder.remove(0);
}
}
Ok(remainder)
}
fn make_monic(mut poly: Vec<BigRational>) -> Vec<BigRational> {
if poly.is_empty() || poly[0].is_zero() {
return poly;
}
let lc = poly[0].clone();
for coeff in &mut poly {
*coeff = &*coeff / &lc;
}
poly
}
fn interpolate_gcd(
&self,
_evaluations: &[Vec<BigRational>],
_points: &[i64],
) -> Result<MultivariatePolynomial, String> {
Ok(MultivariatePolynomial {
terms: vec![],
num_vars: 1,
})
}
fn modular_gcd(
&mut self,
f: &MultivariatePolynomial,
g: &MultivariatePolynomial,
) -> Result<MultivariatePolynomial, String> {
let primes = vec![32003u64, 32009, 32027, 32029, 32051];
let mut gcd_images = Vec::new();
for &prime in &primes {
self.stats.modular_reductions += 1;
let f_mod = self.reduce_modulo(f, prime)?;
let g_mod = self.reduce_modulo(g, prime)?;
let gcd_mod = self.gcd_finite_field(&f_mod, &g_mod, prime)?;
gcd_images.push((gcd_mod, prime));
}
self.stats.crt_reconstructions += 1;
self.chinese_remainder_reconstruction(&gcd_images)
}
fn reduce_modulo(
&self,
poly: &MultivariatePolynomial,
prime: u64,
) -> Result<MultivariatePolynomial, String> {
let mut reduced_terms = Vec::new();
for term in &poly.terms {
let num = term.coefficient.numer().clone();
let den = term.coefficient.denom().clone();
let coeff_mod = self.rational_mod(&num, &den, prime)?;
if coeff_mod != 0 {
reduced_terms.push(Term {
coefficient: BigRational::from_integer(BigInt::from(coeff_mod)),
monomial: term.monomial.clone(),
});
}
}
Ok(MultivariatePolynomial {
terms: reduced_terms,
num_vars: poly.num_vars,
})
}
fn rational_mod(&self, num: &BigInt, den: &BigInt, prime: u64) -> Result<u64, String> {
let num_mod = (num.clone() % BigInt::from(prime)).to_u64_digits().1;
let den_mod = (den.clone() % BigInt::from(prime)).to_u64_digits().1;
if den_mod.is_empty() || den_mod[0] == 0 {
return Err("Denominator is zero modulo prime".to_string());
}
let den_inv = self.mod_inverse(den_mod[0], prime)?;
let num_val = if num_mod.is_empty() { 0 } else { num_mod[0] };
Ok((num_val * den_inv) % prime)
}
fn mod_inverse(&self, a: u64, m: u64) -> Result<u64, String> {
let (g, x, _) = self.extended_gcd(a as i64, m as i64);
if g != 1 {
return Err("Modular inverse does not exist".to_string());
}
Ok(((x % m as i64 + m as i64) % m as i64) as u64)
}
fn extended_gcd(&self, a: i64, b: i64) -> (i64, i64, i64) {
if b == 0 {
return (a, 1, 0);
}
let (g, x1, y1) = self.extended_gcd(b, a % b);
let x = y1;
let y = x1 - (a / b) * y1;
(g, x, y)
}
fn gcd_finite_field(
&self,
f: &MultivariatePolynomial,
_g: &MultivariatePolynomial,
_prime: u64,
) -> Result<MultivariatePolynomial, String> {
Ok(f.clone())
}
fn chinese_remainder_reconstruction(
&self,
images: &[(MultivariatePolynomial, u64)],
) -> Result<MultivariatePolynomial, String> {
if images.is_empty() {
return Err("No images for CRT reconstruction".to_string());
}
Ok(images[0].0.clone())
}
fn subresultant_gcd(
&mut self,
f: &MultivariatePolynomial,
g: &MultivariatePolynomial,
) -> Result<MultivariatePolynomial, String> {
let main_var = 0;
let f_uni = self.to_univariate(f, main_var);
let g_uni = self.to_univariate(g, main_var);
let mut prs = vec![f_uni, g_uni];
let mut beta = BigRational::from_integer(BigInt::from(-1));
let mut psi = BigRational::from_integer(BigInt::from(-1));
loop {
self.stats.subresultant_steps += 1;
let i = prs.len() - 2;
let j = prs.len() - 1;
if prs[j].is_zero() {
break;
}
let remainder = self.pseudo_remainder(&prs[i], &prs[j])?;
if remainder.is_zero() {
break;
}
let delta = prs[i].degree(main_var) - prs[j].degree(main_var);
let next_remainder = self.scale_by_subresultant(&remainder, &beta, &psi, delta);
prs.push(next_remainder);
let lc_j = prs[j].leading_coefficient(main_var);
beta = (-&lc_j).pow(delta as i32);
if delta > 1 {
psi = (-&lc_j).pow((delta - 1) as i32) / psi.pow((delta - 2) as i32);
} else {
psi = -lc_j;
}
}
let gcd = prs
.iter()
.rev()
.find(|p| !p.is_zero())
.ok_or("No non-zero polynomial in PRS")?;
Ok(gcd.clone())
}
fn to_univariate(
&self,
poly: &MultivariatePolynomial,
_main_var: usize,
) -> MultivariatePolynomial {
poly.clone()
}
fn pseudo_remainder(
&self,
dividend: &MultivariatePolynomial,
_divisor: &MultivariatePolynomial,
) -> Result<MultivariatePolynomial, String> {
Ok(dividend.clone())
}
fn scale_by_subresultant(
&self,
remainder: &MultivariatePolynomial,
_beta: &BigRational,
_psi: &BigRational,
_delta: usize,
) -> MultivariatePolynomial {
remainder.clone()
}
pub fn stats(&self) -> &GcdStats {
&self.stats
}
}
impl MultivariatePolynomial {
pub fn is_zero(&self) -> bool {
self.terms.is_empty() || self.terms.iter().all(|t| t.coefficient.is_zero())
}
pub fn total_degree(&self) -> usize {
self.terms
.iter()
.map(|t| t.monomial.values().sum())
.max()
.unwrap_or(0)
}
pub fn degree(&self, var: usize) -> usize {
self.terms
.iter()
.map(|t| *t.monomial.get(&var).unwrap_or(&0))
.max()
.unwrap_or(0)
}
pub fn leading_coefficient(&self, _var: usize) -> BigRational {
self.terms
.first()
.map(|t| t.coefficient.clone())
.unwrap_or_else(BigRational::zero)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcd_computer_creation() {
let config = GcdConfig::default();
let computer = MultivariateGcdComputer::new(config);
assert_eq!(computer.stats.gcd_computations, 0);
}
#[test]
fn test_zero_polynomial_gcd() {
let mut computer = MultivariateGcdComputer::new(GcdConfig::default());
let f = MultivariatePolynomial {
terms: vec![],
num_vars: 1,
};
let g = MultivariatePolynomial {
terms: vec![Term {
coefficient: BigRational::from_integer(BigInt::from(5)),
monomial: Monomial::default(),
}],
num_vars: 1,
};
let result = computer.gcd(&f, &g).expect("test operation should succeed");
assert_eq!(result.terms.len(), 1);
}
#[test]
fn test_is_sparse() {
let computer = MultivariateGcdComputer::new(GcdConfig::default());
let mut mono_x2 = Monomial::default();
mono_x2.insert(0, 2);
let mut mono_z2 = Monomial::default();
mono_z2.insert(2, 2);
let sparse = MultivariatePolynomial {
terms: vec![
Term {
coefficient: BigRational::one(),
monomial: mono_x2,
},
Term {
coefficient: BigRational::one(),
monomial: mono_z2,
},
],
num_vars: 3,
};
assert!(computer.is_sparse(&sparse));
}
#[test]
fn test_univariate_gcd_simple() {
let computer = MultivariateGcdComputer::new(GcdConfig::default());
let f = vec![
BigRational::one(), BigRational::zero(), -BigRational::one(), ];
let g = vec![
BigRational::one(), -BigRational::one(), ];
let result = computer
.univariate_gcd(&f, &g)
.expect("test operation should succeed");
assert!(!result.is_empty());
}
#[test]
fn test_poly_rem() {
let dividend = vec![
BigRational::one(), BigRational::zero(),
-BigRational::one(),
];
let divisor = vec![
BigRational::one(), -BigRational::one(),
];
let result = MultivariateGcdComputer::poly_rem(÷nd, &divisor)
.expect("test operation should succeed");
assert!(MultivariateGcdComputer::is_zero_poly(&result));
}
#[test]
fn test_make_monic() {
let poly = vec![
BigRational::from_integer(BigInt::from(2)),
BigRational::from_integer(BigInt::from(4)),
];
let monic = MultivariateGcdComputer::make_monic(poly);
assert_eq!(monic[0], BigRational::one());
assert_eq!(monic[1], BigRational::from_integer(BigInt::from(2)));
}
#[test]
fn test_mod_inverse() {
let computer = MultivariateGcdComputer::new(GcdConfig::default());
let inv = computer
.mod_inverse(3, 7)
.expect("test operation should succeed");
assert_eq!((3 * inv) % 7, 1);
}
#[test]
fn test_extended_gcd() {
let computer = MultivariateGcdComputer::new(GcdConfig::default());
let (g, x, y) = computer.extended_gcd(35, 15);
assert_eq!(g, 5);
assert_eq!(35 * x + 15 * y, g);
}
#[test]
fn test_rational_mod() {
let computer = MultivariateGcdComputer::new(GcdConfig::default());
let num = BigInt::from(7);
let den = BigInt::from(3);
let prime = 11;
let result = computer
.rational_mod(&num, &den, prime)
.expect("test operation should succeed");
assert_eq!(result, 6);
}
#[test]
fn test_polynomial_degree() {
let mut monomial = Monomial::default();
monomial.insert(0, 2);
monomial.insert(1, 3);
let poly = MultivariatePolynomial {
terms: vec![Term {
coefficient: BigRational::one(),
monomial,
}],
num_vars: 2,
};
assert_eq!(poly.total_degree(), 5);
assert_eq!(poly.degree(0), 2);
assert_eq!(poly.degree(1), 3);
}
}