use std::collections::BTreeMap;
use rug::ops::NegAssign;
use rug::{Assign, Integer, Rational};
use crate::poly::groebner::ideal::GbPoly;
use crate::poly::groebner::monomial_order::MonomialOrder;
use crate::poly::exponent::{add_or_panic, total_degree_or_panic};
pub fn reduce(f: &GbPoly, gs: &[GbPoly], order: MonomialOrder) -> GbPoly {
Divisors::new(gs, order).reduce(f, None)
}
pub fn s_polynomial(f: &GbPoly, g: &GbPoly, order: MonomialOrder) -> GbPoly {
let lf_exp = match f.leading_exp(order) {
Some(e) => e,
None => return GbPoly::zero(f.n_vars),
};
let lg_exp = match g.leading_exp(order) {
Some(e) => e,
None => return GbPoly::zero(f.n_vars),
};
let lf_coeff = f.leading_coeff(order).unwrap();
let lg_coeff = g.leading_coeff(order).unwrap();
let lcm_exp: Vec<u32> = lf_exp
.iter()
.zip(lg_exp.iter())
.map(|(&a, &b)| a.max(b))
.collect();
let shift_f: Vec<u32> = lcm_exp
.iter()
.zip(lf_exp.iter())
.map(|(l, a)| l - a)
.collect();
let shift_g: Vec<u32> = lcm_exp
.iter()
.zip(lg_exp.iter())
.map(|(l, b)| l - b)
.collect();
let coeff_f = rug::Rational::from(1) / &lf_coeff;
let coeff_g = rug::Rational::from(1) / &lg_coeff;
let term_f = f.mul_monomial(&shift_f, &coeff_f);
let term_g = g.mul_monomial(&shift_g, &coeff_g);
term_f.sub(&term_g)
}
#[inline]
fn encode_shifted(order: MonomialOrder, e: &[u32], shift: &[u32], out: &mut Vec<u32>) {
out.clear();
match order {
MonomialOrder::Lex => out.extend(e.iter().zip(shift).map(|(&a, &b)| add_or_panic(a, b))),
MonomialOrder::GrLex => {
out.push(0);
let mut deg = 0u32;
for (&a, &b) in e.iter().zip(shift) {
let x = add_or_panic(a, b);
deg = add_or_panic(deg, x);
out.push(x);
}
out[0] = deg;
}
MonomialOrder::GRevLex => {
out.push(0);
let mut deg = 0u32;
for (&a, &b) in e.iter().zip(shift).rev() {
let x = add_or_panic(a, b);
deg = add_or_panic(deg, x);
out.push(u32::MAX - x);
}
out[0] = deg;
}
}
}
#[inline]
fn decode(order: MonomialOrder, key: &[u32], out: &mut Vec<u32>) {
out.clear();
match order {
MonomialOrder::Lex => out.extend_from_slice(key),
MonomialOrder::GrLex => out.extend_from_slice(&key[1..]),
MonomialOrder::GRevLex => out.extend(key[1..].iter().rev().map(|k| u32::MAX - k)),
}
}
#[inline]
fn accumulate(
p: &mut BTreeMap<Vec<u32>, Rational>,
key: &[u32],
c: &Rational,
q: Option<&Rational>,
negate: bool,
tmp: &mut Rational,
) {
match q {
Some(q) => tmp.assign(c * q),
None => tmp.assign(c),
}
match p.get_mut(key) {
Some(v) => {
if negate {
*v -= &*tmp;
} else {
*v += &*tmp;
}
if v.cmp0() == std::cmp::Ordering::Equal {
p.remove(key);
}
}
None => {
if negate {
tmp.neg_assign();
}
p.insert(key.to_vec(), std::mem::take(tmp));
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Divisor {
lead: Vec<u32>,
lead_deg: u32,
lc: Rational,
lc_is_one: bool,
tail: Vec<(Vec<u32>, Rational)>,
n_vars: usize,
width: Option<usize>,
int: Option<IntForm>,
}
#[derive(Debug, Clone)]
struct IntForm {
lc: Integer,
tail: Vec<Integer>,
}
impl IntForm {
fn new(lc: &Rational, tail: &[(Vec<u32>, Rational)]) -> Self {
let mut ints =
primitive_integer_multiple(std::iter::once(lc).chain(tail.iter().map(|(_, c)| c)));
let tail = ints.split_off(1);
let lc = ints.pop().expect("leading coefficient");
IntForm { lc, tail }
}
}
fn primitive_integer_multiple<'a>(
coeffs: impl Iterator<Item = &'a Rational> + Clone,
) -> Vec<Integer> {
let mut den = Integer::from(1);
for c in coeffs.clone() {
den.lcm_mut(c.denom());
}
let mut ints: Vec<Integer> = coeffs
.map(|c| {
let mut m = Integer::from(den.div_exact_ref(c.denom()));
m *= c.numer();
m
})
.collect();
let mut content = Integer::new();
for c in &ints {
content.gcd_mut(c);
if content == 1 {
return ints;
}
}
if content != 0 {
for c in &mut ints {
c.div_exact_mut(&content);
}
}
ints
}
const CONTENT_EVERY: u32 = 2;
#[inline]
fn accumulate_int(
p: &mut BTreeMap<Vec<u32>, Integer>,
key: &[u32],
c: &Integer,
q: &Integer,
negate: bool,
tmp: &mut Integer,
) {
tmp.assign(c * q);
match p.get_mut(key) {
Some(v) => {
if negate {
*v -= &*tmp;
} else {
*v += &*tmp;
}
if v.cmp0() == std::cmp::Ordering::Equal {
p.remove(key);
}
}
None => {
if negate {
tmp.neg_assign();
}
p.insert(key.to_vec(), std::mem::take(tmp));
}
}
}
fn remove_content(
p: &mut BTreeMap<Vec<u32>, Integer>,
rem: &mut [(Vec<u32>, Integer)],
g: &mut Integer,
) {
g.assign(0);
for c in p.values().chain(rem.iter().map(|(_, c)| c)) {
g.gcd_mut(c);
if *g == 1 {
return;
}
}
if *g == 0 {
return;
}
for c in p.values_mut().chain(rem.iter_mut().map(|(_, c)| c)) {
c.div_exact_mut(g);
}
}
impl Divisor {
fn new(g: &GbPoly, order: MonomialOrder, fraction_free: bool) -> Option<Self> {
let (lead, lc) = g.leading_term(order)?;
let w = lead.len();
let regular = g.terms.iter().all(|(e, c)| e.len() == w && *c != 0);
let tail: Vec<(Vec<u32>, Rational)> = g
.terms
.iter()
.filter(|(e, _)| *e != lead)
.map(|(e, c)| (e.clone(), c.clone()))
.collect();
let int = (fraction_free && regular).then(|| IntForm::new(lc, &tail));
Some(Divisor {
int,
lead: lead.clone(),
lead_deg: total_degree_or_panic(lead),
lc_is_one: *lc == 1,
lc: lc.clone(),
tail,
n_vars: g.n_vars,
width: regular.then_some(w),
})
}
fn to_poly(&self) -> GbPoly {
let mut terms: BTreeMap<Vec<u32>, Rational> = self.tail.iter().cloned().collect();
terms.insert(self.lead.clone(), self.lc.clone());
GbPoly {
terms,
n_vars: self.n_vars,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Divisors {
order: MonomialOrder,
divs: Vec<Option<Divisor>>,
n_vars: Vec<usize>,
fraction_free: bool,
}
impl Divisors {
pub(crate) fn new(gs: &[GbPoly], order: MonomialOrder) -> Self {
Divisors {
order,
divs: gs.iter().map(|g| Divisor::new(g, order, false)).collect(),
n_vars: gs.iter().map(|g| g.n_vars).collect(),
fraction_free: false,
}
}
pub(crate) fn new_fraction_free(gs: &[GbPoly], order: MonomialOrder) -> Self {
Divisors {
order,
divs: gs.iter().map(|g| Divisor::new(g, order, true)).collect(),
n_vars: gs.iter().map(|g| g.n_vars).collect(),
fraction_free: true,
}
}
pub(crate) fn push(&mut self, g: &GbPoly) {
self.divs
.push(Divisor::new(g, self.order, self.fraction_free));
self.n_vars.push(g.n_vars);
}
pub(crate) fn set(&mut self, i: usize, g: &GbPoly) {
self.divs[i] = Divisor::new(g, self.order, self.fraction_free);
self.n_vars[i] = g.n_vars;
}
pub(crate) fn remove(&mut self, i: usize) {
self.divs.remove(i);
self.n_vars.remove(i);
}
fn polys_except(&self, skip: Option<usize>) -> Vec<GbPoly> {
self.divs
.iter()
.enumerate()
.filter(|&(k, _)| Some(k) != skip)
.map(|(_, d)| d.as_ref().map_or_else(|| GbPoly::zero(0), Divisor::to_poly))
.collect()
}
fn all_width(&self, w: usize, skip: Option<usize>) -> bool {
self.divs
.iter()
.enumerate()
.all(|(k, d)| Some(k) == skip || d.as_ref().map_or(true, |d| d.width == Some(w)))
}
pub(crate) fn reduce(&self, f: &GbPoly, skip: Option<usize>) -> GbPoly {
let order = self.order;
let Some(first) = f.terms.keys().next() else {
return GbPoly::zero(f.n_vars);
};
let w = first.len();
let regular = f.terms.iter().all(|(e, c)| e.len() == w && *c != 0);
if !regular || !self.all_width(w, skip) {
return reduce_reference(f, &self.polys_except(skip), order);
}
let zero_shift = vec![0u32; w];
let mut key = Vec::with_capacity(w + 1);
let mut p = BTreeMap::new();
for (e, c) in &f.terms {
encode_shifted(order, e, &zero_shift, &mut key);
p.insert(key.clone(), c.clone());
}
self.reduce_work(p, f.n_vars, skip)
}
pub(crate) fn reduce_s_pair(&self, i: usize, j: usize) -> GbPoly {
let order = self.order;
let (Some(f), Some(g)) = (&self.divs[i], &self.divs[j]) else {
return GbPoly::zero(self.n_vars[i]);
};
let w = f.lead.len();
if f.width != Some(w) || !self.all_width(w, None) {
let sp = s_polynomial(&f.to_poly(), &g.to_poly(), order);
return reduce_reference(&sp, &self.polys_except(None), order);
}
let lcm: Vec<u32> = f.lead.iter().zip(&g.lead).map(|(a, b)| *a.max(b)).collect();
let shift_f: Vec<u32> = lcm.iter().zip(&f.lead).map(|(l, a)| l - a).collect();
let shift_g: Vec<u32> = lcm.iter().zip(&g.lead).map(|(l, b)| l - b).collect();
let inv_f = (!f.lc_is_one).then(|| Rational::from(1) / &f.lc);
let inv_g = (!g.lc_is_one).then(|| Rational::from(1) / &g.lc);
let mut p = BTreeMap::new();
let mut key = Vec::with_capacity(w + 1);
let mut tmp = Rational::new();
for (e, c) in &f.tail {
encode_shifted(order, e, &shift_f, &mut key);
accumulate(&mut p, &key, c, inv_f.as_ref(), false, &mut tmp);
}
for (e, c) in &g.tail {
encode_shifted(order, e, &shift_g, &mut key);
accumulate(&mut p, &key, c, inv_g.as_ref(), true, &mut tmp);
}
self.reduce_work(p, f.n_vars, None)
}
pub(crate) fn reduce_scaled(&self, f: &GbPoly, skip: Option<usize>) -> GbPoly {
let order = self.order;
let Some(first) = f.terms.keys().next() else {
return GbPoly::zero(f.n_vars);
};
let w = first.len();
let regular = f.terms.iter().all(|(e, c)| e.len() == w && *c != 0);
if !self.fraction_free || !regular || !self.all_width(w, skip) {
return self.reduce(f, skip);
}
let ints = primitive_integer_multiple(f.terms.values());
let zero_shift = vec![0u32; w];
let mut key = Vec::with_capacity(w + 1);
let mut p = BTreeMap::new();
for (e, c) in f.terms.keys().zip(ints) {
encode_shifted(order, e, &zero_shift, &mut key);
p.insert(key.clone(), c);
}
self.reduce_work_int(p, f.n_vars, skip)
}
pub(crate) fn reduce_s_pair_scaled(&self, i: usize, j: usize) -> GbPoly {
let order = self.order;
let (Some(f), Some(g)) = (&self.divs[i], &self.divs[j]) else {
return GbPoly::zero(self.n_vars[i]);
};
let w = f.lead.len();
let (Some(fi), Some(gi)) = (&f.int, &g.int) else {
return self.reduce_s_pair(i, j);
};
if f.width != Some(w) || !self.all_width(w, None) {
return self.reduce_s_pair(i, j);
}
let lcm: Vec<u32> = f.lead.iter().zip(&g.lead).map(|(a, b)| *a.max(b)).collect();
let shift_f: Vec<u32> = lcm.iter().zip(&f.lead).map(|(l, a)| l - a).collect();
let shift_g: Vec<u32> = lcm.iter().zip(&g.lead).map(|(l, b)| l - b).collect();
let h = Integer::from(fi.lc.gcd_ref(&gi.lc));
let cf = Integer::from(gi.lc.div_exact_ref(&h));
let cg = Integer::from(fi.lc.div_exact_ref(&h));
let mut p = BTreeMap::new();
let mut key = Vec::with_capacity(w + 1);
let mut tmp = Integer::new();
for ((e, _), c) in f.tail.iter().zip(&fi.tail) {
encode_shifted(order, e, &shift_f, &mut key);
accumulate_int(&mut p, &key, c, &cf, false, &mut tmp);
}
for ((e, _), c) in g.tail.iter().zip(&gi.tail) {
encode_shifted(order, e, &shift_g, &mut key);
accumulate_int(&mut p, &key, c, &cg, true, &mut tmp);
}
self.reduce_work_int(p, f.n_vars, None)
}
fn reduce_work_int(
&self,
mut p: BTreeMap<Vec<u32>, Integer>,
n_vars: usize,
skip: Option<usize>,
) -> GbPoly {
let order = self.order;
let is_graded = order.is_graded();
let mut rem: Vec<(Vec<u32>, Integer)> = Vec::new();
let mut last_divisor = 0usize;
let mut lt_exp: Vec<u32> = Vec::new();
let mut shift: Vec<u32> = Vec::new();
let mut key: Vec<u32> = Vec::new();
let mut tmp = Integer::new();
let mut h = Integer::new();
let mut mp = Integer::new();
let mut scalings = 0u32;
while let Some((lt_key, mut mg)) = p.pop_last() {
decode(order, <_key, &mut lt_exp);
let lt_deg = if is_graded { lt_key[0] } else { 0 };
let Some((idx, d)) = self.find_divisor(<_exp, lt_deg, last_divisor, skip) else {
rem.push((lt_exp.clone(), mg));
continue;
};
let di = d.int.as_ref().expect("fraction-free divisor");
h.assign(mg.gcd_ref(&di.lc));
mp.assign(di.lc.div_exact_ref(&h));
mg.div_exact_mut(&h);
if mp != 1 {
for v in p.values_mut() {
*v *= ∓
}
for (_, v) in &mut rem {
*v *= ∓
}
scalings += 1;
}
shift.clear();
shift.extend(lt_exp.iter().zip(&d.lead).map(|(a, b)| a - b));
for ((e, _), c) in d.tail.iter().zip(&di.tail) {
encode_shifted(order, e, &shift, &mut key);
accumulate_int(&mut p, &key, c, &mg, true, &mut tmp);
}
last_divisor = idx;
if scalings >= CONTENT_EVERY {
scalings = 0;
remove_content(&mut p, &mut rem, &mut tmp);
}
}
GbPoly {
terms: rem
.into_iter()
.map(|(e, c)| (e, Rational::from(c)))
.collect(),
n_vars,
}
}
#[inline]
fn find_divisor(
&self,
lt_exp: &[u32],
lt_deg: u32,
last: usize,
skip: Option<usize>,
) -> Option<(usize, &Divisor)> {
let is_graded = self.order.is_graded();
let n_divs = self.divs.len();
for offset in 0..n_divs {
let idx = (last + offset) % n_divs;
if Some(idx) == skip {
continue;
}
let Some(d) = &self.divs[idx] else {
continue;
};
if is_graded && d.lead_deg > lt_deg {
continue;
}
if lt_exp.iter().zip(&d.lead).all(|(a, b)| a >= b) {
return Some((idx, d));
}
}
None
}
fn reduce_work(
&self,
mut p: BTreeMap<Vec<u32>, Rational>,
n_vars: usize,
skip: Option<usize>,
) -> GbPoly {
let order = self.order;
let is_graded = order.is_graded();
let mut rem: Vec<(Vec<u32>, Rational)> = Vec::new();
let mut last_divisor = 0usize;
let mut lt_exp: Vec<u32> = Vec::new();
let mut shift: Vec<u32> = Vec::new();
let mut key: Vec<u32> = Vec::new();
let mut tmp = Rational::new();
while let Some((lt_key, mut q)) = p.pop_last() {
decode(order, <_key, &mut lt_exp);
let lt_deg = if is_graded { lt_key[0] } else { 0 };
let Some((idx, d)) = self.find_divisor(<_exp, lt_deg, last_divisor, skip) else {
rem.push((lt_exp.clone(), q));
continue;
};
if !d.lc_is_one {
q /= &d.lc;
}
shift.clear();
shift.extend(lt_exp.iter().zip(&d.lead).map(|(a, b)| a - b));
for (e, c) in &d.tail {
encode_shifted(order, e, &shift, &mut key);
accumulate(&mut p, &key, c, Some(&q), true, &mut tmp);
}
last_divisor = idx;
}
GbPoly {
terms: rem.into_iter().collect(),
n_vars,
}
}
}
pub(crate) fn reduce_reference(f: &GbPoly, gs: &[GbPoly], order: MonomialOrder) -> GbPoly {
let mut p = f.clone();
let mut r = GbPoly::zero(f.n_vars);
let mut last_divisor: usize = 0;
let is_graded = order.is_graded();
'outer: while !p.is_zero() {
let (lt_exp, lt_coeff) = match p.leading_term(order) {
Some((e, c)) => (e.clone(), c.clone()),
None => break,
};
let lt_deg: u32 = if is_graded {
total_degree_or_panic(<_exp)
} else {
0
};
for offset in 0..gs.len() {
let idx = (last_divisor + offset) % gs.len();
let g = &gs[idx];
if let Some((lg_exp, lg_coeff)) = g.leading_term(order) {
if is_graded && total_degree_or_panic(lg_exp) > lt_deg {
continue;
}
if lt_exp.len() == lg_exp.len()
&& lt_exp.iter().zip(lg_exp.iter()).all(|(a, b)| a >= b)
{
let shift: Vec<u32> = lt_exp
.iter()
.zip(lg_exp.iter())
.map(|(a, b)| a - b)
.collect();
let coeff = rug::Rational::from(<_coeff / lg_coeff);
let subtrahend = g.mul_monomial(&shift, &coeff);
p = p.sub(&subtrahend);
last_divisor = idx;
continue 'outer;
}
}
}
let lt = GbPoly::monomial(lt_exp.clone(), lt_coeff.clone());
r = r.add(<);
let mut p_terms = p.terms.clone();
p_terms.remove(<_exp);
p.terms = p_terms;
}
r
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn rat(n: i64, d: i64) -> rug::Rational {
rug::Rational::from((n, d))
}
const ORDERS: [MonomialOrder; 3] = [
MonomialOrder::Lex,
MonomialOrder::GrLex,
MonomialOrder::GRevLex,
];
fn assert_same(a: &GbPoly, b: &GbPoly) {
assert_eq!(a.terms, b.terms);
assert_eq!(a.n_vars, b.n_vars);
}
fn arb_poly(n: usize, max_terms: usize) -> impl Strategy<Value = GbPoly> {
let term = (proptest::collection::vec(0u32..=3, n), -4i64..=4, 1i64..=3);
proptest::collection::vec(term, 0..=max_terms).prop_map(move |ts| {
let mut p = GbPoly::zero(n);
for (e, num, den) in ts {
p = p.add(&GbPoly::monomial(e, rat(num, den)));
}
p
})
}
fn arb_problem() -> impl Strategy<Value = (GbPoly, Vec<GbPoly>, usize)> {
(1usize..=3).prop_flat_map(|n| {
(
arb_poly(n, 8),
proptest::collection::vec(arb_poly(n, 4), 0..=4),
0usize..3,
)
})
}
#[test]
fn encoding_preserves_every_order() {
let mut exps = vec![];
for a in 0..3u32 {
for b in 0..3u32 {
for c in 0..3u32 {
exps.push(vec![a, b, c]);
}
}
}
let zero = [0u32; 3];
for order in ORDERS {
for x in &exps {
let (mut kx, mut back) = (vec![], vec![]);
encode_shifted(order, x, &zero, &mut kx);
decode(order, &kx, &mut back);
assert_eq!(&back, x);
for y in &exps {
let mut ky = vec![];
encode_shifted(order, y, &zero, &mut ky);
assert_eq!(kx.cmp(&ky), order.cmp(x, y), "{order:?} {x:?} {y:?}");
}
}
}
}
#[test]
fn irregular_inputs_take_the_reference_path() {
let f = GbPoly::monomial(vec![2, 1], rat(3, 2));
let gs = vec![
GbPoly::monomial(vec![1], rat(1, 1)),
GbPoly {
terms: [(vec![1, 0], rat(2, 1)), (vec![0, 1], rat(1, 1))]
.into_iter()
.collect(),
n_vars: 2,
},
];
for order in ORDERS {
assert_same(&reduce(&f, &gs, order), &reduce_reference(&f, &gs, order));
}
let f = GbPoly {
terms: [(vec![0, 3], rat(0, 1)), (vec![2, 0], rat(1, 1))]
.into_iter()
.collect(),
n_vars: 2,
};
let gs = vec![GbPoly::monomial(vec![1, 0], rat(1, 1))];
for order in ORDERS {
assert_same(&reduce(&f, &gs, order), &reduce_reference(&f, &gs, order));
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn reduce_matches_reference((f, gs, ord) in arb_problem()) {
let order = ORDERS[ord];
let new = reduce(&f, &gs, order);
let old = reduce_reference(&f, &gs, order);
prop_assert_eq!(&new.terms, &old.terms);
prop_assert_eq!(new.n_vars, old.n_vars);
}
#[test]
fn reduce_skip_matches_removal((f, gs, ord) in arb_problem(), k in 0usize..4) {
prop_assume!(k < gs.len());
let order = ORDERS[ord];
let new = Divisors::new(&gs, order).reduce(&f, Some(k));
let mut others = gs.clone();
others.remove(k);
let old = reduce_reference(&f, &others, order);
prop_assert_eq!(&new.terms, &old.terms);
}
#[test]
fn reduce_s_pair_matches_reference((_f, gs, ord) in arb_problem(), i in 0usize..12, j in 0usize..12) {
prop_assume!(gs.len() >= 2);
let (i, j) = (i % gs.len(), j % gs.len());
prop_assume!(i != j);
let order = ORDERS[ord];
let new = Divisors::new(&gs, order).reduce_s_pair(i, j);
let sp = s_polynomial(&gs[i], &gs[j], order);
let old = reduce_reference(&sp, &gs, order);
prop_assert_eq!(&new.terms, &old.terms);
prop_assert_eq!(new.n_vars, old.n_vars);
}
#[test]
fn reduce_scaled_is_a_multiple((f, gs, ord) in arb_problem(), k in 0usize..5) {
let order = ORDERS[ord];
let skip = (k < gs.len()).then_some(k);
let divs = Divisors::new_fraction_free(&gs, order);
let scaled = divs.reduce_scaled(&f, skip);
let exact = divs.reduce(&f, skip);
prop_assert_eq!(scaled.n_vars, exact.n_vars);
prop_assert_eq!(&scaled.make_monic(order).terms, &exact.make_monic(order).terms);
}
#[test]
fn reduce_s_pair_scaled_is_a_multiple((_f, gs, ord) in arb_problem(), i in 0usize..12, j in 0usize..12) {
prop_assume!(gs.len() >= 2);
let (i, j) = (i % gs.len(), j % gs.len());
prop_assume!(i != j);
let order = ORDERS[ord];
let divs = Divisors::new_fraction_free(&gs, order);
let scaled = divs.reduce_s_pair_scaled(i, j);
let exact = divs.reduce_s_pair(i, j);
prop_assert_eq!(scaled.n_vars, exact.n_vars);
prop_assert_eq!(&scaled.make_monic(order).terms, &exact.make_monic(order).terms);
}
}
#[test]
fn reduce_to_remainder() {
let f = GbPoly::monomial(vec![2, 0], rat(1, 1));
let g = GbPoly {
terms: [(vec![2, 0], rat(1, 1)), (vec![0, 0], rat(-1, 1))]
.into_iter()
.collect(),
n_vars: 2,
};
let r = reduce(&f, &[g], MonomialOrder::Lex);
assert!(!r.is_zero());
}
#[test]
fn s_polynomial_example() {
let f = GbPoly {
terms: [(vec![2], rat(1, 1)), (vec![0], rat(-1, 1))]
.into_iter()
.collect(),
n_vars: 1,
};
let g = GbPoly {
terms: [(vec![1], rat(1, 1)), (vec![0], rat(-1, 1))]
.into_iter()
.collect(),
n_vars: 1,
};
let s = s_polynomial(&f, &g, MonomialOrder::Lex);
assert!(!s.is_zero());
}
}