pub mod f4;
use std::collections::HashSet;
use ocas_domain::Domain;
use crate::sparse::{
MonomialOrder, SparseMultivariatePolynomial, monomial_are_coprime, monomial_divides,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroebnerBasis<D: Domain, O: MonomialOrder> {
pub basis: Vec<SparseMultivariatePolynomial<D, O>>,
}
impl<D: Domain, O: MonomialOrder> GroebnerBasis<D, O> {
pub fn buchberger(ideal: &[SparseMultivariatePolynomial<D, O>]) -> Self {
let mut basis: Vec<SparseMultivariatePolynomial<D, O>> =
ideal.iter().filter(|p| !p.is_zero()).cloned().collect();
if basis.is_empty() {
return Self { basis };
}
let mut pairs: HashSet<(usize, usize)> = HashSet::new();
for i in 0..basis.len() {
for j in i + 1..basis.len() {
pairs.insert((i, j));
}
}
let max_iter = 10000;
for _ in 0..max_iter {
if pairs.is_empty() {
break;
}
let (i, j) = *pairs.iter().next().unwrap();
pairs.remove(&(i, j));
let lm_i = basis[i].leading_monomial();
let lm_j = basis[j].leading_monomial();
if let (Some(mi), Some(mj)) = (&lm_i, &lm_j)
&& monomial_are_coprime(mi, mj)
{
continue;
}
let s = basis[i].spoly(&basis[j]);
let r = s.reduce(&basis);
if !r.is_zero() {
let new_idx = basis.len();
basis.push(r);
for k in 0..new_idx {
pairs.insert((k, new_idx));
}
}
}
Self { basis }
}
pub fn minimize(mut self) -> Self {
let lms: Vec<_> = self
.basis
.iter()
.filter_map(|p| p.leading_monomial().cloned())
.collect();
let mut keep = vec![true; self.basis.len()];
for i in 0..self.basis.len() {
for j in 0..self.basis.len() {
if i != j && keep[i] && keep[j] && monomial_divides(&lms[i], &lms[j]) {
keep[i] = false;
break;
}
}
}
self.basis = self
.basis
.into_iter()
.enumerate()
.filter(|(i, _)| keep[*i])
.map(|(_, p)| p)
.collect();
self
}
pub fn auto_reduce(mut self) -> Self {
self.basis
.sort_by(|a, b| match (a.leading_monomial(), b.leading_monomial()) {
(Some(ma), Some(mb)) => O::cmp(ma, mb),
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal,
});
let mut reduced: Vec<SparseMultivariatePolynomial<D, O>> = Vec::new();
for poly in &self.basis {
let mut r = poly.reduce(&reduced);
if !r.is_zero() {
if let Some(lc) = r.leading_coeff().cloned()
&& let Some(inv) = r.domain().inv(&lc)
{
r = r.mul_scalar(&inv);
}
reduced.push(r);
}
}
self.basis = reduced;
self
}
pub fn is_groebner_basis(&self) -> bool {
for i in 0..self.basis.len() {
for j in i + 1..self.basis.len() {
let s = self.basis[i].spoly(&self.basis[j]);
let r = s.reduce(&self.basis);
if !r.is_zero() {
return false;
}
}
}
true
}
}
pub fn buchberger<D: Domain, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
) -> GroebnerBasis<D, O> {
GroebnerBasis::buchberger(ideal).minimize().auto_reduce()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse::Lex;
use ocas_domain::{Rational, RationalDomain};
fn r(n: i64, d: i64) -> Rational {
Rational::new(n, d)
}
fn make_poly(
terms: Vec<(Vec<usize>, Rational)>,
) -> SparseMultivariatePolynomial<RationalDomain, Lex> {
SparseMultivariatePolynomial::from_terms(RationalDomain, 2, terms)
}
#[test]
fn empty_ideal() {
let gb = buchberger::<RationalDomain, Lex>(&[]);
assert!(gb.basis.is_empty());
}
#[test]
fn single_polynomial() {
let f = SparseMultivariatePolynomial::<_, Lex>::from_terms(
RationalDomain,
1,
vec![(vec![2], r(1, 1)), (vec![0], r(-1, 1))],
);
let gb = buchberger(&[f]);
assert_eq!(gb.basis.len(), 1);
assert!(gb.is_groebner_basis());
}
#[test]
fn linear_system() {
let f1 = make_poly(vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(1, 1))]);
let f2 = make_poly(vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(-1, 1))]);
let gb = buchberger(&[f1, f2]);
assert!(gb.is_groebner_basis());
assert!(gb.basis.len() >= 2);
}
#[test]
fn two_variable_ideal() {
let f1 = make_poly(vec![(vec![2, 0], r(1, 1)), (vec![0, 1], r(-1, 1))]);
let f2 = make_poly(vec![(vec![3, 0], r(1, 1)), (vec![1, 0], r(-1, 1))]);
let gb = buchberger(&[f1, f2]);
assert!(gb.is_groebner_basis());
assert!(!gb.basis.is_empty());
}
}