use crate::poly::multipoly::*;
use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
pub fn groebner_basis<O: MonomialOrd>(polys: &[MultiPoly<O>]) -> Vec<MultiPoly<O>> {
let nonzero: Vec<MultiPoly<O>> = polys.iter().filter(|p| !p.is_zero()).cloned().collect();
if nonzero.is_empty() {
return vec![];
}
for p in &nonzero {
if p.total_degree() == Some(0) {
let n = p.num_vars();
return vec![MultiPoly::from_int(n, 1)];
}
}
buchberger_core(&nonzero)
}
fn buchberger_core<O: MonomialOrd>(input: &[MultiPoly<O>]) -> Vec<MultiPoly<O>> {
let mut basis: Vec<MultiPoly<O>> = pre_reduce(input);
if basis.is_empty() {
return vec![];
}
for p in &basis {
if p.total_degree() == Some(0) {
let n = p.num_vars();
return vec![MultiPoly::from_int(n, 1)];
}
}
let mut pairs: BTreeSet<(usize, usize)> = BTreeSet::new();
for i in 0..basis.len() {
for j in (i + 1)..basis.len() {
pairs.insert((i, j));
}
}
let mut processed: HashSet<(usize, usize)> = HashSet::new();
while let Some((i, j)) = select_pair(&pairs, &basis) {
pairs.remove(&(i, j));
if i >= basis.len() || j >= basis.len() {
continue;
}
let lm_i = match basis[i].leading_monomial() {
Some(lm) => lm.to_vec(),
None => {
processed.insert((i, j));
continue;
}
};
let lm_j = match basis[j].leading_monomial() {
Some(lm) => lm.to_vec(),
None => {
processed.insert((i, j));
continue;
}
};
if monomial_coprime(&lm_i, &lm_j) {
processed.insert((i, j));
continue;
}
let lcm_ij = monomial_lcm(&lm_i, &lm_j);
let chain_skip = (0..basis.len()).any(|k| {
if k == i || k == j {
return false;
}
if let Some(lm_k) = basis[k].leading_monomial() {
if monomial_divides(lm_k, &lcm_ij) {
let pair_ik = if i < k { (i, k) } else { (k, i) };
let pair_jk = if j < k { (j, k) } else { (k, j) };
processed.contains(&pair_ik) && processed.contains(&pair_jk)
} else {
false
}
} else {
false
}
});
if chain_skip {
processed.insert((i, j));
continue;
}
let s = s_polynomial(&basis[i], &basis[j]);
let basis_refs: Vec<&MultiPoly<O>> = basis.iter().collect();
let remainder = s.reduce(&basis_refs);
processed.insert((i, j));
if !remainder.is_zero() {
let h = remainder.monic().primitive_part_q();
if h.total_degree() == Some(0) {
let n = h.num_vars();
return vec![MultiPoly::from_int(n, 1)];
}
let h_idx = basis.len();
update_pairs(&mut pairs, &basis, &h);
basis.push(h);
for k in 0..h_idx {
pairs.insert((k, h_idx));
}
}
}
inter_reduce(&mut basis);
basis.sort_by(|a, b| {
let lm_a = a.leading_monomial().unwrap_or(&[]);
let lm_b = b.leading_monomial().unwrap_or(&[]);
O::cmp_exponents(lm_b, lm_a)
});
basis
}
fn select_pair<O: MonomialOrd>(
pairs: &BTreeSet<(usize, usize)>,
basis: &[MultiPoly<O>],
) -> Option<(usize, usize)> {
pairs
.iter()
.filter_map(|&(i, j)| {
let lm_i = basis.get(i)?.leading_monomial()?;
let lm_j = basis.get(j)?.leading_monomial()?;
Some(((i, j), monomial_lcm(lm_i, lm_j)))
})
.min_by(|(_, lcm1), (_, lcm2)| O::cmp_exponents(lcm1, lcm2))
.map(|(pair, _)| pair)
}
fn pre_reduce<O: MonomialOrd>(input: &[MultiPoly<O>]) -> Vec<MultiPoly<O>> {
let mut result: Vec<MultiPoly<O>> = input
.iter()
.filter(|p| !p.is_zero())
.map(|p| p.monic())
.collect();
loop {
let mut changed = false;
let mut new_result: Vec<MultiPoly<O>> = Vec::new();
for item in &result {
let others: Vec<&MultiPoly<O>> = new_result.iter().collect();
let reduced = if others.is_empty() {
item.clone()
} else {
item.reduce(&others)
};
if reduced.is_zero() {
changed = true; } else {
let r = reduced.monic();
if r != *item {
changed = true;
}
new_result.push(r);
}
}
result = new_result;
if !changed {
break;
}
}
result
}
fn inter_reduce<O: MonomialOrd>(basis: &mut Vec<MultiPoly<O>>) {
basis.retain(|p| !p.is_zero());
let mut i = 0;
while i < basis.len() {
let lm_i = match basis[i].leading_monomial() {
Some(lm) => lm.to_vec(),
None => {
basis.remove(i);
continue;
}
};
let redundant = (0..basis.len()).any(|j| {
if j == i {
return false;
}
match basis[j].leading_monomial() {
Some(lm_j) => monomial_divides(lm_j, &lm_i) && lm_j != lm_i,
None => false,
}
});
if redundant {
basis.remove(i);
} else {
i += 1;
}
}
let mut seen_lms: HashSet<Vec<u32>> = HashSet::new();
basis.retain(|p| {
if let Some(lm) = p.leading_monomial() {
seen_lms.insert(lm.to_vec())
} else {
false
}
});
for i in 0..basis.len() {
let others: Vec<MultiPoly<O>> = basis
.iter()
.enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, p)| p.clone())
.collect();
let refs: Vec<&MultiPoly<O>> = others.iter().collect();
if !refs.is_empty() {
let reduced = basis[i].reduce(&refs);
if !reduced.is_zero() {
basis[i] = reduced.monic();
}
}
}
basis.retain(|p| !p.is_zero());
}
fn update_pairs<O: MonomialOrd>(
pairs: &mut BTreeSet<(usize, usize)>,
basis: &[MultiPoly<O>],
h: &MultiPoly<O>,
) {
let lm_h = match h.leading_monomial() {
Some(lm) => lm,
None => return,
};
let pairs_to_remove: Vec<(usize, usize)> = pairs
.iter()
.filter(|&&(i, j)| {
if i >= basis.len() || j >= basis.len() {
return false;
}
let lm_i = match basis[i].leading_monomial() {
Some(lm) => lm,
None => return false,
};
let lm_j = match basis[j].leading_monomial() {
Some(lm) => lm,
None => return false,
};
let lcm_ij = monomial_lcm(lm_i, lm_j);
if monomial_divides(lm_h, &lcm_ij) {
let lcm_ih = monomial_lcm(lm_i, lm_h);
let lcm_jh = monomial_lcm(lm_j, lm_h);
lcm_ih != lcm_ij && lcm_jh != lcm_ij
} else {
false
}
})
.copied()
.collect();
for pair in pairs_to_remove {
pairs.remove(&pair);
}
}
pub fn is_groebner_basis<O: MonomialOrd>(basis: &[MultiPoly<O>]) -> bool {
if basis.is_empty() {
return true;
}
let refs: Vec<&MultiPoly<O>> = basis.iter().collect();
for i in 0..basis.len() {
for j in (i + 1)..basis.len() {
if basis[i].is_zero() || basis[j].is_zero() {
continue;
}
let s = s_polynomial(&basis[i], &basis[j]);
let r = s.reduce(&refs);
if !r.is_zero() {
return false;
}
}
}
true
}
pub fn is_zero_dimensional<O: MonomialOrd>(basis: &[MultiPoly<O>]) -> bool {
if basis.is_empty() {
return false;
}
let n = basis[0].num_vars();
for var in 0..n {
let has_pure_power = basis.iter().any(|p| {
if let Some(lm) = p.leading_monomial() {
lm.iter().enumerate().all(|(i, &e)| i == var || e == 0) && lm[var] > 0
} else {
false
}
});
if !has_pure_power {
return false;
}
}
true
}
fn standard_monomials<O: MonomialOrd>(basis: &[MultiPoly<O>], num_vars: usize) -> Vec<Vec<u32>> {
let leading_monomials: Vec<Vec<u32>> = basis
.iter()
.filter_map(|p| p.leading_monomial().map(|m| m.to_vec()))
.collect();
let mut staircase = Vec::new();
let mut queue: VecDeque<Vec<u32>> = VecDeque::new();
queue.push_back(vec![0u32; num_vars]); let mut visited: HashSet<Vec<u32>> = HashSet::new();
while let Some(mono) = queue.pop_front() {
if visited.contains(&mono) {
continue;
}
visited.insert(mono.clone());
let is_divisible = leading_monomials
.iter()
.any(|lm| monomial_divides(lm, &mono));
if !is_divisible {
staircase.push(mono.clone());
for var in 0..num_vars {
let mut next = mono.clone();
next[var] += 1;
if !visited.contains(&next) {
queue.push_back(next);
}
}
}
}
staircase
}
pub fn fglm<From: MonomialOrd, To: MonomialOrd>(
basis: &[MultiPoly<From>],
) -> Option<Vec<MultiPoly<To>>> {
if basis.is_empty() {
return Some(vec![]);
}
let n = basis[0].num_vars();
if !is_zero_dimensional(basis) {
return None;
}
let staircase = standard_monomials(basis, n);
let d = staircase.len();
if d == 0 {
return Some(vec![MultiPoly::from_int(n, 1)]);
}
let staircase_idx: HashMap<Vec<u32>, usize> = staircase
.iter()
.enumerate()
.map(|(i, m)| (m.clone(), i))
.collect();
let basis_refs: Vec<&MultiPoly<From>> = basis.iter().collect();
let mut mult_matrices: Vec<Vec<Vec<Ratio<BigInt>>>> = Vec::with_capacity(n);
for var in 0..n {
let mut matrix = vec![vec![Ratio::<BigInt>::zero(); d]; d]; for j in 0..d {
let mut product_exp = staircase[j].clone();
product_exp[var] += 1;
let product = MultiPoly::<From>::monomial(Ratio::one(), product_exp);
let nf = product.reduce(&basis_refs);
for (key, coeff) in nf.terms() {
if let Some(&idx) = staircase_idx.get(key) {
matrix[idx][j] = coeff.clone();
}
}
}
mult_matrices.push(matrix);
}
let mut new_basis: Vec<MultiPoly<To>> = Vec::new();
let mut to_staircase: Vec<Vec<u32>> = Vec::new();
let mut echelon = IncrementalEchelon::new(d);
let mut nf_cache: HashMap<Vec<u32>, Vec<Ratio<BigInt>>> = HashMap::new();
let zero_mono = vec![0u32; n];
let mut nf_one = vec![Ratio::<BigInt>::zero(); d];
if let Some(&idx) = staircase_idx.get(&zero_mono) {
nf_one[idx] = Ratio::one();
}
nf_cache.insert(zero_mono.clone(), nf_one);
let mut candidates: BTreeSet<MonoKey<To>> = BTreeSet::new();
candidates.insert(MonoKey::new(zero_mono));
let mut visited_to: HashSet<Vec<u32>> = HashSet::new();
while let Some(mono_key) = candidates.iter().next().cloned() {
candidates.remove(&mono_key);
let mono = mono_key.exponents.clone();
if visited_to.contains(&mono) {
continue;
}
visited_to.insert(mono.clone());
let is_in_ideal = new_basis.iter().any(|p| {
if let Some(lm) = p.leading_monomial() {
monomial_divides(lm, &mono)
} else {
false
}
});
if is_in_ideal {
continue; }
let nf = compute_nf_via_matrices(&mono, &mult_matrices, &nf_cache, n, d);
nf_cache.insert(mono.clone(), nf.clone());
match echelon.add(&nf) {
EchelonResult::Independent => {
to_staircase.push(mono.clone());
for var in 0..n {
let mut next = mono.clone();
next[var] += 1;
if !visited_to.contains(&next) {
candidates.insert(MonoKey::<To>::new(next));
}
}
}
EchelonResult::Dependent(coeffs) => {
let mut new_poly = MultiPoly::<To>::monomial(Ratio::one(), mono.clone());
for (i, c) in coeffs.iter().enumerate() {
if !c.is_zero() {
let term = MultiPoly::<To>::monomial(-c.clone(), to_staircase[i].clone());
new_poly = new_poly.add(&term);
}
}
new_basis.push(new_poly.monic());
}
}
if to_staircase.len() >= d {
}
}
inter_reduce(&mut new_basis);
new_basis.sort_by(|a, b| {
let lm_a = a.leading_monomial().unwrap_or(&[]);
let lm_b = b.leading_monomial().unwrap_or(&[]);
To::cmp_exponents(lm_b, lm_a)
});
Some(new_basis)
}
fn compute_nf_via_matrices(
mono: &[u32],
mult_matrices: &[Vec<Vec<Ratio<BigInt>>>],
nf_cache: &HashMap<Vec<u32>, Vec<Ratio<BigInt>>>,
n: usize,
d: usize,
) -> Vec<Ratio<BigInt>> {
if let Some(nf) = nf_cache.get(mono) {
return nf.clone();
}
for var in 0..n {
if mono[var] > 0 {
let mut parent = mono.to_vec();
parent[var] -= 1;
if let Some(parent_nf) = nf_cache.get(&parent) {
return matrix_vector_mul(&mult_matrices[var], parent_nf, d);
}
}
}
let zero_mono = vec![0u32; n];
let base_nf = match nf_cache.get(&zero_mono) {
Some(nf) => nf.clone(),
None => {
let mut nf = vec![Ratio::<BigInt>::zero(); d];
if d > 0 {
nf[0] = Ratio::one();
}
nf
}
};
let mut current_nf = base_nf;
for var in 0..n {
for _ in 0..mono[var] {
current_nf = matrix_vector_mul(&mult_matrices[var], ¤t_nf, d);
}
}
current_nf
}
fn matrix_vector_mul(
matrix: &[Vec<Ratio<BigInt>>],
vec: &[Ratio<BigInt>],
d: usize,
) -> Vec<Ratio<BigInt>> {
let mut result = vec![Ratio::<BigInt>::zero(); d];
for i in 0..d {
for j in 0..d {
if !vec[j].is_zero() && !matrix[i][j].is_zero() {
result[i] = &result[i] + &(&matrix[i][j] * &vec[j]);
}
}
}
result
}
enum EchelonResult {
Independent,
Dependent(Vec<Ratio<BigInt>>),
}
type EchelonRow = (usize, Vec<Ratio<BigInt>>, Vec<Ratio<BigInt>>);
struct IncrementalEchelon {
d: usize,
rows: Vec<EchelonRow>,
num_independent: usize,
}
impl IncrementalEchelon {
fn new(d: usize) -> Self {
Self {
d,
rows: Vec::new(),
num_independent: 0,
}
}
fn add(&mut self, nf: &[Ratio<BigInt>]) -> EchelonResult {
let m = self.num_independent;
let d = self.d;
let mut v = nf.to_vec();
let mut trail = vec![Ratio::<BigInt>::zero(); m];
for (pivot_col, row_vec, row_coeff) in &self.rows {
if !v[*pivot_col].is_zero() {
let factor = v[*pivot_col].clone() / &row_vec[*pivot_col];
for k in 0..d {
if !row_vec[k].is_zero() {
let sub = &factor * &row_vec[k];
v[k] = &v[k] - ⊂
}
}
for k in 0..row_coeff.len().min(trail.len()) {
if !row_coeff[k].is_zero() {
let add = &factor * &row_coeff[k];
trail[k] = &trail[k] + &add;
}
}
}
}
match v.iter().position(|c| !c.is_zero()) {
None => EchelonResult::Dependent(trail),
Some(pivot_col) => {
let mut coeff: Vec<Ratio<BigInt>> = trail.iter().map(|t| -t.clone()).collect();
coeff.push(Ratio::one());
self.rows.push((pivot_col, v, coeff));
self.num_independent += 1;
EchelonResult::Independent
}
}
}
}
pub fn groebner_basis_lex(polys: &[MultiPoly<GrevLex>]) -> Vec<MultiPoly<Lex>> {
let grevlex_gb = groebner_basis(polys);
if grevlex_gb.is_empty() {
return vec![];
}
if let Some(lex_gb) = fglm::<GrevLex, Lex>(&grevlex_gb) {
return lex_gb;
}
let lex_polys: Vec<MultiPoly<Lex>> = polys.iter().map(|p| p.convert_order()).collect();
groebner_basis(&lex_polys)
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Ratio<BigInt> {
Ratio::from_integer(BigInt::from(n))
}
#[test]
fn test_pre_reduce_simple() {
let x = MultiPoly::<GrevLex>::var(2, 0);
let y = MultiPoly::<GrevLex>::var(2, 1);
let _one = MultiPoly::<GrevLex>::from_int(2, 1);
let p1 = &x + &y;
let p2 = x.clone();
let result = pre_reduce(&[p1, p2]);
assert!(!result.is_empty());
}
#[test]
fn test_inter_reduce_removes_redundant() {
let x = MultiPoly::<GrevLex>::var(2, 0);
let x2 = &x * &x;
let mut basis = vec![x2, x];
inter_reduce(&mut basis);
assert_eq!(basis.len(), 1);
}
#[test]
fn test_standard_monomials_simple() {
let x2 = MultiPoly::<GrevLex>::monomial(Ratio::one(), vec![2, 0]);
let y2 = MultiPoly::<GrevLex>::monomial(Ratio::one(), vec![0, 2]);
let basis = vec![x2, y2];
let sm = standard_monomials(&basis, 2);
assert_eq!(sm.len(), 4);
}
#[test]
fn test_echelon_independent() {
let mut ech = IncrementalEchelon::new(3);
let v1 = vec![r(1), r(0), r(0)];
match ech.add(&v1) {
EchelonResult::Independent => {}
_ => panic!("should be independent"),
}
let v2 = vec![r(0), r(1), r(0)];
match ech.add(&v2) {
EchelonResult::Independent => {}
_ => panic!("should be independent"),
}
}
#[test]
fn test_echelon_dependent() {
let mut ech = IncrementalEchelon::new(2);
let v1 = vec![r(1), r(0)];
match ech.add(&v1) {
EchelonResult::Independent => {}
_ => panic!("should be independent"),
}
let v2 = vec![r(0), r(1)];
match ech.add(&v2) {
EchelonResult::Independent => {}
_ => panic!("should be independent"),
}
let v3 = vec![r(3), r(2)];
match ech.add(&v3) {
EchelonResult::Dependent(coeffs) => {
assert_eq!(coeffs.len(), 2);
assert_eq!(coeffs[0], r(3));
assert_eq!(coeffs[1], r(2));
}
_ => panic!("should be dependent"),
}
}
}