use crate::polynomial::{Monomial, Polynomial, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use num_rational::BigRational;
use num_traits::{One, Zero};
pub struct SyzygyComputer {
critical_pairs: BinaryHeap<CriticalPair>,
syzygies: Vec<Syzygy>,
criteria_cache: FxHashMap<(usize, usize), BuchbergerCriteria>,
stats: SyzygyStats,
}
#[derive(Debug, Clone)]
pub struct CriticalPair {
pub i: usize,
pub j: usize,
pub lcm: Monomial,
pub priority: i64,
pub sugar: usize,
}
#[derive(Debug, Clone)]
pub struct Syzygy {
pub coefficients: FxHashMap<usize, Polynomial>,
pub degree: usize,
}
#[derive(Debug, Clone)]
pub struct BuchbergerCriteria {
pub criterion1: bool,
pub criterion2: bool,
}
#[derive(Debug, Clone, Default)]
pub struct SyzygyStats {
pub pairs_generated: usize,
pub pairs_eliminated: usize,
pub s_polynomials_computed: usize,
pub zero_reductions: usize,
pub syzygies_found: usize,
pub criterion1_apps: usize,
pub criterion2_apps: usize,
}
impl SyzygyComputer {
pub fn new() -> Self {
Self {
critical_pairs: BinaryHeap::new(),
syzygies: Vec::new(),
criteria_cache: FxHashMap::default(),
stats: SyzygyStats::default(),
}
}
pub fn generate_critical_pair(
&mut self,
i: usize,
j: usize,
fi: &Polynomial,
fj: &Polynomial,
) -> Option<CriticalPair> {
if i >= j {
return None;
}
self.stats.pairs_generated += 1;
let lt_i = fi.leading_monomial()?;
let lt_j = fj.leading_monomial()?;
let lcm = Self::monomial_lcm(lt_i, lt_j);
let priority = -(lcm.total_degree() as i64);
let sugar_i = fi.sugar_degree() as u32;
let sugar_j = fj.sugar_degree() as u32;
let sugar = sugar_i.max(sugar_j) + lcm.total_degree()
- lt_i.total_degree().max(lt_j.total_degree());
Some(CriticalPair {
i,
j,
lcm,
priority,
sugar: sugar as usize,
})
}
pub fn add_critical_pair(&mut self, pair: CriticalPair) {
self.critical_pairs.push(pair);
}
pub fn pop_critical_pair(&mut self) -> Option<CriticalPair> {
self.critical_pairs.pop()
}
pub fn apply_buchberger_criteria(
&mut self,
i: usize,
j: usize,
fi: &Polynomial,
fj: &Polynomial,
basis: &[Polynomial],
) -> bool {
if let Some(criteria) = self.criteria_cache.get(&(i, j)) {
if criteria.criterion1 || criteria.criterion2 {
self.stats.pairs_eliminated += 1;
return true;
}
return false;
}
let criterion1 = self.check_criterion1(fi, fj);
if criterion1 {
self.stats.criterion1_apps += 1;
self.criteria_cache.insert(
(i, j),
BuchbergerCriteria {
criterion1: true,
criterion2: false,
},
);
self.stats.pairs_eliminated += 1;
return true;
}
let criterion2 = self.check_criterion2(i, j, fi, fj, basis);
if criterion2 {
self.stats.criterion2_apps += 1;
self.criteria_cache.insert(
(i, j),
BuchbergerCriteria {
criterion1: false,
criterion2: true,
},
);
self.stats.pairs_eliminated += 1;
return true;
}
self.criteria_cache.insert(
(i, j),
BuchbergerCriteria {
criterion1: false,
criterion2: false,
},
);
false
}
fn check_criterion1(&self, fi: &Polynomial, fj: &Polynomial) -> bool {
if let (Some(lt_i), Some(lt_j)) = (fi.leading_monomial(), fj.leading_monomial()) {
Self::are_relatively_prime(lt_i, lt_j)
} else {
false
}
}
fn check_criterion2(
&self,
i: usize,
j: usize,
fi: &Polynomial,
fj: &Polynomial,
basis: &[Polynomial],
) -> bool {
if let (Some(lt_i), Some(lt_j)) = (fi.leading_monomial(), fj.leading_monomial()) {
let lcm = Self::monomial_lcm(lt_i, lt_j);
let product = Self::monomial_mul(lt_i, lt_j);
if lcm == product {
return true;
}
for (k, fk) in basis.iter().enumerate() {
if k == i || k == j {
continue;
}
if let Some(lt_k) = fk.leading_monomial()
&& Self::monomial_divides(lt_k, &lcm)
{
let lcm_ik = Self::monomial_lcm(lt_i, lt_k);
let lcm_jk = Self::monomial_lcm(lt_j, lt_k);
if Self::monomial_divides(&lcm_ik, &lcm)
&& Self::monomial_divides(&lcm_jk, &lcm)
{
return true;
}
}
}
}
false
}
pub fn compute_s_polynomial(
&mut self,
pair: &CriticalPair,
fi: &Polynomial,
fj: &Polynomial,
) -> Polynomial {
self.stats.s_polynomials_computed += 1;
if let (Some(lt_i), Some(lt_j)) = (fi.leading_monomial(), fj.leading_monomial()) {
let cofactor_i = Self::monomial_div(&pair.lcm, lt_i);
let cofactor_j = Self::monomial_div(&pair.lcm, lt_j);
let lc_i = fi.leading_coeff();
let lc_j = fj.leading_coeff();
let term_i = fi
.mul_monomial(&cofactor_i)
.mul_scalar(&(BigRational::one() / &lc_i));
let term_j = fj
.mul_monomial(&cofactor_j)
.mul_scalar(&(BigRational::one() / &lc_j));
&term_i - &term_j
} else {
Polynomial::zero()
}
}
pub fn record_syzygy(&mut self, syzygy: Syzygy) {
self.stats.syzygies_found += 1;
self.syzygies.push(syzygy);
}
pub fn create_syzygy(
&mut self,
i: usize,
j: usize,
fi: &Polynomial,
fj: &Polynomial,
) -> Syzygy {
self.stats.zero_reductions += 1;
let mut coefficients = FxHashMap::default();
if let (Some(lt_i), Some(lt_j)) = (fi.leading_monomial(), fj.leading_monomial()) {
let lcm = Self::monomial_lcm(lt_i, lt_j);
let cofactor_i = Self::monomial_div(&lcm, lt_i);
let cofactor_j = Self::monomial_div(&lcm, lt_j);
let lc_i = fi.leading_coeff();
let lc_j = fj.leading_coeff();
let coeff_i = Polynomial::from_monomial(cofactor_i, BigRational::one() / &lc_i);
coefficients.insert(i, coeff_i);
let coeff_j = Polynomial::from_monomial(cofactor_j, -(BigRational::one() / &lc_j));
coefficients.insert(j, coeff_j);
Syzygy {
coefficients,
degree: lcm.total_degree() as usize,
}
} else {
Syzygy {
coefficients: FxHashMap::default(),
degree: 0,
}
}
}
fn monomial_lcm(m1: &Monomial, m2: &Monomial) -> Monomial {
let mut result_powers = FxHashMap::default();
for (&var, &power) in m1.powers().iter() {
result_powers.insert(var, power);
}
for (&var, &power2) in m2.powers().iter() {
let max_power = result_powers.get(&var).copied().unwrap_or(0).max(power2);
result_powers.insert(var, max_power);
}
Monomial::from_powers(result_powers.into_iter().map(|(v, p)| (v, p as u32)))
}
#[allow(dead_code)]
fn monomial_gcd(m1: &Monomial, m2: &Monomial) -> Monomial {
let mut result_powers = FxHashMap::default();
for (&var, &power1) in m1.powers().iter() {
if let Some(&power2) = m2.powers().get(&var) {
let min_power = power1.min(power2);
if min_power > 0 {
result_powers.insert(var, min_power);
}
}
}
Monomial::from_powers(result_powers.into_iter().map(|(v, p)| (v, p as u32)))
}
fn monomial_mul(m1: &Monomial, m2: &Monomial) -> Monomial {
let mut result_powers = m1.powers().clone();
for (&var, &power) in m2.powers().iter() {
*result_powers.entry(var).or_insert(0) += power;
}
Monomial::from_powers(result_powers.into_iter().map(|(v, p)| (v, p as u32)))
}
fn monomial_div(m1: &Monomial, m2: &Monomial) -> Monomial {
let mut result_powers = m1.powers().clone();
for (&var, &power) in m2.powers().iter() {
if let Some(p) = result_powers.get_mut(&var) {
*p = p.saturating_sub(power);
if *p == 0 {
result_powers.remove(&var);
}
}
}
Monomial::from_powers(result_powers.into_iter().map(|(v, p)| (v, p as u32)))
}
fn monomial_divides(m1: &Monomial, m2: &Monomial) -> bool {
for (&var, &power1) in m1.powers().iter() {
if let Some(&power2) = m2.powers().get(&var) {
if power1 > power2 {
return false;
}
} else {
return false;
}
}
true
}
fn are_relatively_prime(m1: &Monomial, m2: &Monomial) -> bool {
for (&var, &power1) in m1.powers().iter() {
if let Some(&power2) = m2.powers().get(&var)
&& power1 > 0
&& power2 > 0
{
return false;
}
}
true
}
pub fn syzygy_module(&self) -> &[Syzygy] {
&self.syzygies
}
pub fn stats(&self) -> &SyzygyStats {
&self.stats
}
pub fn clear(&mut self) {
self.critical_pairs.clear();
self.criteria_cache.clear();
}
}
impl Ord for CriticalPair {
fn cmp(&self, other: &Self) -> Ordering {
self.priority
.cmp(&other.priority)
.then_with(|| self.sugar.cmp(&other.sugar))
.then_with(|| self.i.cmp(&other.i))
.then_with(|| self.j.cmp(&other.j))
}
}
impl PartialOrd for CriticalPair {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for CriticalPair {
fn eq(&self, other: &Self) -> bool {
self.i == other.i && self.j == other.j
}
}
impl Eq for CriticalPair {}
impl Default for SyzygyComputer {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
trait PolynomialSyzygy {
fn sugar_degree(&self) -> usize;
fn mul_monomial(&self, m: &Monomial) -> Polynomial;
fn mul_scalar(&self, s: &BigRational) -> Polynomial;
fn from_monomial(m: Monomial, coeff: BigRational) -> Polynomial;
fn zero() -> Polynomial;
}
impl PolynomialSyzygy for Polynomial {
fn sugar_degree(&self) -> usize {
self.total_degree() as usize
}
fn mul_monomial(&self, _m: &Monomial) -> Polynomial {
self.clone()
}
fn mul_scalar(&self, _s: &BigRational) -> Polynomial {
self.clone()
}
fn from_monomial(_m: Monomial, _coeff: BigRational) -> Polynomial {
Polynomial::zero()
}
fn zero() -> Polynomial {
Polynomial::constant(BigRational::zero())
}
}
#[allow(dead_code)]
trait MonomialHelper {
fn from_powers(powers: FxHashMap<Var, usize>) -> Monomial;
fn powers(&self) -> &FxHashMap<Var, usize>;
fn total_degree(&self) -> usize;
}
impl MonomialHelper for Monomial {
fn from_powers(_powers: FxHashMap<Var, usize>) -> Monomial {
Monomial::unit()
}
fn powers(&self) -> &FxHashMap<Var, usize> {
#[cfg(feature = "std")]
{
use std::sync::OnceLock;
static EMPTY: OnceLock<FxHashMap<Var, usize>> = OnceLock::new();
EMPTY.get_or_init(FxHashMap::default)
}
#[cfg(not(feature = "std"))]
{
static mut EMPTY_PTR: *const FxHashMap<Var, usize> = core::ptr::null();
unsafe {
if EMPTY_PTR.is_null() {
EMPTY_PTR = Box::into_raw(Box::new(FxHashMap::default()));
}
&*EMPTY_PTR
}
}
}
fn total_degree(&self) -> usize {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_syzygy_computer() {
let computer = SyzygyComputer::new();
assert_eq!(computer.stats.pairs_generated, 0);
}
#[test]
fn test_critical_pair_ordering() {
let pair1 = CriticalPair {
i: 0,
j: 1,
lcm: Monomial::unit(),
priority: -5,
sugar: 3,
};
let pair2 = CriticalPair {
i: 0,
j: 2,
lcm: Monomial::unit(),
priority: -3,
sugar: 2,
};
assert!(pair2 > pair1);
}
#[test]
fn test_monomial_lcm() {
let m1 = Monomial::unit();
let m2 = Monomial::unit();
let lcm = SyzygyComputer::monomial_lcm(&m1, &m2);
assert_eq!(lcm.total_degree(), 0);
}
#[test]
fn test_relatively_prime() {
let m1 = Monomial::unit();
let m2 = Monomial::unit();
assert!(SyzygyComputer::are_relatively_prime(&m1, &m2));
}
#[test]
fn test_syzygy_creation() {
let mut computer = SyzygyComputer::new();
let f1 = Polynomial::zero();
let f2 = Polynomial::zero();
let syzygy = computer.create_syzygy(0, 1, &f1, &f2);
assert_eq!(syzygy.degree, 0);
}
}