use std::collections::BinaryHeap;
use crate::poly::groebner::ideal::GbPoly;
use crate::poly::groebner::monomial_order::MonomialOrder;
use crate::poly::groebner::reduce::{reduce, s_polynomial};
#[inline]
fn lcm_exp(a: &[u32], b: &[u32]) -> Vec<u32> {
a.iter().zip(b.iter()).map(|(&x, &y)| x.max(y)).collect()
}
#[inline]
fn monomial_divides(a: &[u32], b: &[u32]) -> bool {
a.iter().zip(b.iter()).all(|(ai, bi)| ai <= bi)
}
#[inline]
fn total_deg(e: &[u32]) -> u32 {
e.iter().sum()
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct CriticalPair {
sugar_deg: u32,
lcm_deg: u32,
lcm_exp: Vec<u32>,
i: usize,
j: usize,
}
impl Ord for CriticalPair {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.sugar_deg
.cmp(&self.sugar_deg)
.then_with(|| other.lcm_deg.cmp(&self.lcm_deg))
.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<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn update_pairs(
basis: &[GbPoly],
basis_sugar: &[u32],
pairs: &mut Vec<CriticalPair>,
new_idx: usize,
order: MonomialOrder,
) {
let lh = match basis[new_idx].leading_exp(order) {
Some(e) => e,
None => return,
};
let lh_deg = total_deg(&lh);
let ecart_h = basis_sugar[new_idx].saturating_sub(lh_deg);
struct Cand {
g_idx: usize,
lcm: Vec<u32>,
ecart_g: u32,
}
let candidates: Vec<Cand> = (0..new_idx)
.filter_map(|g_idx| {
let lg = basis[g_idx].leading_exp(order)?;
if lh.iter().zip(lg.iter()).all(|(&a, &b)| a == 0 || b == 0) {
return None;
}
let ecart_g = basis_sugar[g_idx].saturating_sub(total_deg(&lg));
Some(Cand {
g_idx,
lcm: lcm_exp(&lh, &lg),
ecart_g,
})
})
.collect();
let c_min: Vec<&Cand> = candidates
.iter()
.filter(|ci| {
!candidates.iter().any(|cj| {
cj.g_idx != ci.g_idx && monomial_divides(&cj.lcm, &ci.lcm) && cj.lcm != ci.lcm
})
})
.collect();
pairs.retain(|p| {
let lg1 = match basis[p.i].leading_exp(order) {
Some(e) => e,
None => return false,
};
let lg2 = match basis[p.j].leading_exp(order) {
Some(e) => e,
None => return false,
};
let lcm_12 = lcm_exp(&lg1, &lg2);
if !monomial_divides(&lh, &lcm_12) {
return true; }
if lcm_exp(&lg1, &lh) == lcm_12 {
return true; }
if lcm_exp(&lg2, &lh) == lcm_12 {
return true; }
false });
for c in c_min {
let lcm_deg = total_deg(&c.lcm);
let sugar_deg = lcm_deg + c.ecart_g.max(ecart_h);
pairs.push(CriticalPair {
sugar_deg,
lcm_deg,
lcm_exp: c.lcm.clone(),
i: c.g_idx,
j: new_idx,
});
}
}
pub fn compute_buchberger_basis(generators: Vec<GbPoly>, order: MonomialOrder) -> Vec<GbPoly> {
let initial: Vec<GbPoly> = generators
.into_iter()
.filter(|g| !g.is_zero())
.map(|g| g.make_monic(order))
.collect();
if initial.is_empty() {
return initial;
}
let mut basis: Vec<GbPoly> = Vec::with_capacity(initial.len() * 2);
let mut basis_sugar: Vec<u32> = Vec::with_capacity(initial.len() * 2);
let mut pair_vec: Vec<CriticalPair> = Vec::new();
for gen in initial {
let sugar = gen.sugar();
let new_idx = basis.len();
basis.push(gen);
basis_sugar.push(sugar);
update_pairs(&basis, &basis_sugar, &mut pair_vec, new_idx, order);
}
let mut heap: BinaryHeap<CriticalPair> = BinaryHeap::from(pair_vec);
while let Some(pair) = heap.pop() {
let sp = s_polynomial(&basis[pair.i], &basis[pair.j], order);
let r = reduce(&sp, &basis, order);
if !r.is_zero() {
let r = r.make_monic(order);
let sugar = r.sugar();
let new_idx = basis.len();
basis.push(r);
basis_sugar.push(sugar);
let mut pv: Vec<CriticalPair> = heap.into_vec();
update_pairs(&basis, &basis_sugar, &mut pv, new_idx, order);
heap = BinaryHeap::from(pv);
}
}
interreduce(basis, order)
}
pub(crate) fn interreduce(mut basis: Vec<GbPoly>, order: MonomialOrder) -> Vec<GbPoly> {
let mut i = 0;
while i < basis.len() {
let others: Vec<GbPoly> = basis
.iter()
.enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, g)| g.clone())
.collect();
let reduced = reduce(&basis[i], &others, order);
if reduced.is_zero() {
basis.remove(i);
} else {
basis[i] = reduced.make_monic(order);
i += 1;
}
}
basis
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64, d: i64) -> rug::Rational {
rug::Rational::from((n, d))
}
fn poly(terms: &[(&[u32], i64)]) -> GbPoly {
let n_vars = terms.first().map(|(e, _)| e.len()).unwrap_or(1);
GbPoly {
terms: terms
.iter()
.map(|(e, c)| (e.to_vec(), rat(*c, 1)))
.collect(),
n_vars,
}
}
#[test]
fn groebner_x_squared_minus_1_and_x_minus_1() {
let f = poly(&[(&[2], 1), (&[0], -1)]);
let g = poly(&[(&[1], 1), (&[0], -1)]);
let basis = compute_buchberger_basis(vec![f, g], MonomialOrder::Lex);
assert_eq!(basis.len(), 1);
assert!(basis[0].terms.contains_key(&vec![1]));
}
#[test]
fn groebner_trivial_ideal() {
let f = poly(&[(&[1, 0], 1)]);
let g = poly(&[(&[0, 1], 1)]);
let basis = compute_buchberger_basis(vec![f, g], MonomialOrder::GRevLex);
assert_eq!(basis.len(), 2);
}
#[test]
fn groebner_linear_system() {
let f = poly(&[(&[1, 0], 1), (&[0, 1], 1), (&[0, 0], -1)]);
let g = poly(&[(&[1, 0], 1), (&[0, 1], -1)]);
let basis = compute_buchberger_basis(vec![f, g], MonomialOrder::Lex);
assert!(!basis.is_empty());
let orig_f = poly(&[(&[1, 0], 1), (&[0, 1], 1), (&[0, 0], -1)]);
let orig_g = poly(&[(&[1, 0], 1), (&[0, 1], -1)]);
let r_f = reduce(&orig_f, &basis, MonomialOrder::Lex);
let r_g = reduce(&orig_g, &basis, MonomialOrder::Lex);
assert!(r_f.is_zero(), "f does not reduce to 0 mod basis: {:?}", r_f);
assert!(r_g.is_zero(), "g does not reduce to 0 mod basis: {:?}", r_g);
}
#[test]
fn contains_check() {
let f = poly(&[(&[1, 0], 1)]);
let basis = compute_buchberger_basis(vec![f], MonomialOrder::Lex);
let zero = GbPoly::zero(2);
let r = reduce(&zero, &basis, MonomialOrder::Lex);
assert!(r.is_zero());
}
#[test]
fn circle_parabola_grevlex() {
let x2_y2_m4 = poly(&[(&[2, 0], 1), (&[0, 2], 1), (&[0, 0], -4)]);
let y_mx2_p1 = poly(&[(&[0, 1], 1), (&[2, 0], -1), (&[0, 0], 1)]);
let basis =
compute_buchberger_basis(vec![x2_y2_m4, y_mx2_p1.clone()], MonomialOrder::GRevLex);
assert!(!basis.is_empty());
let r = reduce(&y_mx2_p1, &basis, MonomialOrder::GRevLex);
assert!(r.is_zero());
}
}