use std::collections::HashMap;
use ocas_domain::Domain;
use crate::groebner::GroebnerBasis;
use crate::sparse::{MonomialOrder, SparseMultivariatePolynomial, monomial_divides, monomial_lcm};
#[derive(Clone)]
struct LabeledPoly<D: Domain, O: MonomialOrder> {
poly: SparseMultivariatePolynomial<D, O>,
module_pos: usize,
multiplier: Vec<usize>,
}
impl<D: Domain, O: MonomialOrder> LabeledPoly<D, O> {
fn signature(&self) -> (usize, &[usize]) {
(self.module_pos, &self.multiplier)
}
}
pub fn f5<D: Domain, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
) -> GroebnerBasis<D, O> {
let polys: Vec<SparseMultivariatePolynomial<D, O>> =
ideal.iter().filter(|p| !p.is_zero()).cloned().collect();
if polys.is_empty() {
return GroebnerBasis { basis: vec![] };
}
let mut basis: Vec<LabeledPoly<D, O>> = Vec::new();
let mut out: Vec<SparseMultivariatePolynomial<D, O>> = Vec::new();
for (idx, f) in polys.iter().enumerate() {
let labeled = LabeledPoly {
poly: f.clone(),
module_pos: idx,
multiplier: vec![0; f.n_vars()],
};
out = f5_incremental(&basis, labeled, &out);
basis = out
.iter()
.map(|p| LabeledPoly {
poly: p.clone(),
module_pos: idx,
multiplier: vec![0; p.n_vars()],
})
.collect();
}
GroebnerBasis { basis: out }.minimize().auto_reduce()
}
fn f5_incremental<D: Domain, O: MonomialOrder>(
basis: &[LabeledPoly<D, O>],
new: LabeledPoly<D, O>,
current: &[SparseMultivariatePolynomial<D, O>],
) -> Vec<SparseMultivariatePolynomial<D, O>> {
let mut out: Vec<SparseMultivariatePolynomial<D, O>> = current.to_vec();
let mut labeled: Vec<LabeledPoly<D, O>> = basis.to_vec();
labeled.push(new);
out.push(labeled.last().unwrap().poly.clone());
let n = labeled.len() - 1;
let mut pairs: Vec<(usize, usize)> = Vec::new();
for i in 0..n {
pairs.push((i, n));
}
let max_iter = 10000;
let mut iter = 0;
while let Some((i, j)) = pairs.pop() {
iter += 1;
if iter > max_iter {
break;
}
if is_rewritable(&labeled, i, j) {
continue;
}
let s = labeled[i].poly.spoly(&labeled[j].poly);
let r = s.reduce(&out);
if !r.is_zero() {
let new_idx = out.len();
out.push(r.clone());
labeled.push(LabeledPoly {
poly: r,
module_pos: labeled[j].module_pos,
multiplier: labeled[j].multiplier.clone(),
});
for k in 0..new_idx {
pairs.push((k, new_idx));
}
}
}
out
}
fn is_rewritable<D: Domain, O: MonomialOrder>(
labeled: &[LabeledPoly<D, O>],
i: usize,
j: usize,
) -> bool {
let (pos_i, m_i) = labeled[i].signature();
let (pos_j, m_j) = labeled[j].signature();
if pos_i == pos_j {
let lm_i = labeled[i].poly.leading_monomial();
let lm_j = labeled[j].poly.leading_monomial();
if let (Some(a), Some(b)) = (lm_i, lm_j) {
let lcm = monomial_lcm(a, b);
let _ = (m_i, m_j);
if monomial_divides(&lcm, a) || monomial_divides(&lcm, b) {
return true;
}
}
}
false
}
#[allow(dead_code)]
type SignatureMap<D, O> = HashMap<(usize, Vec<usize>), SparseMultivariatePolynomial<D, O>>;
#[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)
}
#[test]
fn f5_linear_system() {
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(-1, 1))],
);
let gb = f5(&[f1, f2]);
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_two_variable_ideal() {
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![2, 0], r(1, 1)), (vec![0, 1], r(-1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![3, 0], r(1, 1)), (vec![1, 0], r(-1, 1))],
);
let gb = f5(&[f1, f2]);
assert!(gb.is_groebner_basis());
}
}