Skip to main content

feanor_math/algorithms/
buchberger.rs

1use append_only_vec::AppendOnlyVec;
2
3use crate::computation::*;
4use crate::delegate::{UnwrapHom, WrapHom};
5use crate::divisibility::{DivisibilityRingStore, DivisibilityRing};
6use crate::field::Field;
7use crate::homomorphism::Homomorphism;
8use crate::local::{PrincipalLocalRing, PrincipalLocalRingStore};
9use crate::pid::PrincipalIdealRingStore;
10use crate::ring::*;
11use crate::seq::*;
12use crate::rings::multivariate::*;
13
14use std::cmp::min;
15use std::fmt::Debug;
16
17#[stability::unstable(feature = "enable")]
18#[derive(PartialEq, Clone, Eq, Hash)]
19pub enum SPoly {
20    Standard(usize, usize), Nilpotent(/* poly index */ usize, /* power-of-p multiplier */ usize)
21}
22
23impl Debug for SPoly {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            SPoly::Standard(i, j) => write!(f, "S({}, {})", i, j),
27            SPoly::Nilpotent(i, k) => write!(f, "p^{} F({})", k, i)
28        }
29    }
30}
31
32fn term_xlcm<P>(ring: P, (l_c, l_m): (&PolyCoeff<P>, &PolyMonomial<P>), (r_c, r_m): (&PolyCoeff<P>, &PolyMonomial<P>)) -> ((PolyCoeff<P>, PolyMonomial<P>), (PolyCoeff<P>, PolyMonomial<P>), (PolyCoeff<P>, PolyMonomial<P>))
33    where P: RingStore,
34        P::Type: MultivariatePolyRing,
35        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing
36{
37    let d_c = ring.base_ring().ideal_gen(l_c, r_c);
38    let m_m = ring.monomial_lcm(ring.clone_monomial(l_m), r_m);
39    let l_factor = ring.base_ring().checked_div(r_c, &d_c).unwrap();
40    let r_factor = ring.base_ring().checked_div(l_c, &d_c).unwrap();
41    let m_c = ring.base_ring().mul_ref_snd(ring.base_ring().mul_ref_snd(d_c, &r_factor), &l_factor);
42    return (
43        (l_factor, ring.monomial_div(ring.clone_monomial(&m_m), &l_m).ok().unwrap()),
44        (r_factor, ring.monomial_div(ring.clone_monomial(&m_m), &r_m).ok().unwrap()),
45        (m_c, m_m)
46    );
47}
48
49fn term_lcm<P>(ring: P, (l_c, l_m): (&PolyCoeff<P>, &PolyMonomial<P>), (r_c, r_m): (&PolyCoeff<P>, &PolyMonomial<P>)) -> (PolyCoeff<P>, PolyMonomial<P>)
50    where P: RingStore,
51        P::Type: MultivariatePolyRing,
52        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing
53{
54    let d_c = ring.base_ring().ideal_gen(l_c, r_c);
55    let m_m = ring.monomial_lcm(ring.clone_monomial(l_m), r_m);
56    let l_factor = ring.base_ring().checked_div(r_c, &d_c).unwrap();
57    let r_factor = ring.base_ring().checked_div(l_c, &d_c).unwrap();
58    let m_c = ring.base_ring().mul_ref_snd(ring.base_ring().mul_ref_snd(d_c, &r_factor), &l_factor);
59    return (m_c, m_m);
60}
61
62impl SPoly {
63
64    #[stability::unstable(feature = "enable")]
65    pub fn lcm_term<P, O>(&self, ring: P, basis: &[El<P>], order: O) -> (PolyCoeff<P>, PolyMonomial<P>)
66        where P: RingStore + Copy,
67            P::Type: MultivariatePolyRing,
68            <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
69            O: MonomialOrder + Copy
70    {
71        match self {
72            SPoly::Standard(i, j) => term_lcm(&ring, ring.LT(&basis[*i], order).unwrap(), ring.LT(&basis[*j], order).unwrap()),
73            Self::Nilpotent(i, k) => {
74                let (c, m) = ring.LT(&basis[*i], order).unwrap();
75                (ring.base_ring().mul_ref_fst(c, ring.base_ring().pow(ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()), *k)), ring.clone_monomial(m))
76            }
77        }
78    }
79
80    
81    #[stability::unstable(feature = "enable")]
82    pub fn poly<P, O>(&self, ring: P, basis: &[El<P>], order: O) -> El<P>
83        where P: RingStore + Copy,
84            P::Type: MultivariatePolyRing,
85            <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
86            O: MonomialOrder + Copy
87    {
88        match self {
89            SPoly::Standard(i, j) => {
90                let (f1_factor, f2_factor, _) = term_xlcm(&ring, ring.LT(&basis[*i], order).unwrap(), ring.LT(&basis[*j], order).unwrap());
91                let mut f1_scaled = ring.clone_el(&basis[*i]);
92                ring.mul_assign_monomial(&mut f1_scaled, f1_factor.1);
93                ring.inclusion().mul_assign_map(&mut f1_scaled, f1_factor.0);
94                let mut f2_scaled = ring.clone_el(&basis[*j]);
95                ring.mul_assign_monomial(&mut f2_scaled, f2_factor.1);
96                ring.inclusion().mul_assign_map(&mut f2_scaled, f2_factor.0);
97                return ring.sub(f1_scaled, f2_scaled);
98            },
99            SPoly::Nilpotent(i, k) => {
100                let mut result = ring.clone_el(&basis[*i]);
101                ring.inclusion().mul_assign_map(&mut result, ring.base_ring().pow(ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()), *k));
102                return result;
103            }
104        }
105    }
106}
107
108#[inline(never)]
109fn find_reducer<'a, 'b, P, O, I>(ring: P, f: &El<P>, reducers: I, order: O) -> Option<(usize, &'a El<P>, PolyCoeff<P>, PolyMonomial<P>)>
110    where P: RingStore + Copy,
111        P::Type: MultivariatePolyRing,
112        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
113        O: MonomialOrder + Copy,
114        I: Iterator<Item = (&'a El<P>, &'b ExpandedMonomial)>
115{
116    if ring.is_zero(&f) {
117        return None;
118    }
119    let (f_lc, f_lm) = ring.LT(f, order).unwrap();
120    let f_lm_expanded = ring.expand_monomial(f_lm);
121    reducers.enumerate().filter_map(|(i, (reducer, reducer_lm_expanded))| {
122        if (0..ring.indeterminate_count()).all(|j| reducer_lm_expanded[j] <= f_lm_expanded[j]) {
123            let (r_lc, r_lm) = ring.LT(reducer, order).unwrap();
124            let quo_m = ring.monomial_div(ring.clone_monomial(f_lm), r_lm).ok().unwrap();
125            if let Some(quo_c) = ring.base_ring().checked_div(f_lc, r_lc) {
126                return Some((i, reducer, quo_c, quo_m));
127            }
128        }
129        return None;
130    }).next()
131}
132
133#[inline(never)]
134fn filter_spoly<P, O>(ring: P, new_spoly: SPoly, basis: &[El<P>], order: O) -> Option<usize>
135    where P: RingStore + Copy,
136        P::Type: MultivariatePolyRing,
137        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
138        O: MonomialOrder + Copy
139{
140    match new_spoly {
141        SPoly::Standard(i, k) => {
142            assert!(i < k);
143            let (bi_c, bi_m) = ring.LT(&basis[i], order).unwrap();
144            let (bk_c, bk_m) = ring.LT(&basis[k], order).unwrap();
145            let (S_c, S_m) = term_lcm(ring, (bi_c, bi_m), (bk_c, bk_m));
146            let S_c_val = ring.base_ring().valuation(&S_c).unwrap();
147
148            if S_c_val == 0 && order.eq_mon(ring, &ring.monomial_div(ring.clone_monomial(&S_m), &bi_m).ok().unwrap(), &bk_m) {
149                return Some(usize::MAX);
150            }
151
152            (0..k).filter_map(|j| {
153                if j == i {
154                    return None;
155                }
156                // more experiments needed - for some weird reason, replacing "properly divides" with "divides" (assuming
157                // I didn't make a mistake) leads to terrible performance
158                let (bj_c, bj_m) = ring.LT(&basis[j], order).unwrap();
159                let (f_c, f_m) = term_lcm(ring, (bj_c, bj_m), (bk_c, bk_m));
160                let f_c_val = ring.base_ring().valuation(&f_c).unwrap();
161
162                if j < i && order.eq_mon(ring, &f_m, &S_m) && f_c_val <= S_c_val {
163                    return Some(j);
164                }
165                if let Ok(quo) = ring.monomial_div(ring.clone_monomial(&S_m), &f_m) {
166                    if f_c_val <= S_c_val && (f_c_val < S_c_val || ring.monomial_deg(&quo) > 0) {
167                        return Some(j);
168                    }
169                }
170                return None;
171            }).next()
172        },
173        SPoly::Nilpotent(i, k) => {
174            let nilpotent_power = ring.base_ring().nilpotent_power().unwrap();
175            let f = &basis[i];
176
177            let mut smallest_elim_coeff_valuation = usize::MAX;
178            let mut current = ring.LT(f, order).unwrap();
179            while ring.base_ring().valuation(&current.0).unwrap() + k >= nilpotent_power {
180                smallest_elim_coeff_valuation = min(smallest_elim_coeff_valuation, ring.base_ring().valuation(&current.0).unwrap());
181                let next = ring.largest_term_lt(f, order, &current.1);
182                if next.is_none() {
183                    return Some(usize::MAX);
184                }
185                current = next.unwrap();
186            }
187            assert!(smallest_elim_coeff_valuation == usize::MAX || smallest_elim_coeff_valuation + k >= nilpotent_power);
188            if smallest_elim_coeff_valuation == usize::MAX || smallest_elim_coeff_valuation + k > nilpotent_power {
189                return Some(usize::MAX);
190            } else {
191                return None;
192            }
193        }
194    }
195}
196
197#[stability::unstable(feature = "enable")]
198pub fn default_sort_fn<P, O>(ring: P, order: O) -> impl FnMut(&mut [SPoly], &[El<P>])
199    where P: RingStore + Copy,
200        P::Type: MultivariatePolyRing,
201        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
202        O: MonomialOrder + Copy
203{
204    move |open, basis| open.sort_by_key(|spoly| {
205        let (lc, lm) = spoly.lcm_term(ring, &basis, order);
206        (-(ring.base_ring().valuation(&lc).unwrap_or(0) as i64), -(ring.monomial_deg(&lm) as i64))
207    })
208}
209
210#[stability::unstable(feature = "enable")]
211pub type ExpandedMonomial = Vec<usize>;
212
213fn augment_lm<P, O>(ring: P, f: El<P>, order: O) -> (El<P>, ExpandedMonomial)
214    where P: RingStore + Copy,
215        P::Type: MultivariatePolyRing,
216        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
217        O: MonomialOrder
218{
219    let exponents = ring.expand_monomial(ring.LT(&f, order).unwrap().1);
220    return (f, exponents);
221}
222
223///
224/// Computes a Groebner basis of the ideal generated by the input basis w.r.t. the given term ordering.
225/// 
226/// For a variant of this function that uses sensible defaults for most parameters, see [`buchberger_simple()`].
227/// 
228/// The algorithm proceeds F4-style, i.e. reduces multiple S-polynomials before adding them to the basis.
229/// When using a fast polynomial ring implementation (e.g. [`crate::rings::multivariate::multivariate_impl::MultivariatePolyRingImpl`]),
230/// this makes the algorithm as efficient as standard F4. Furthermore, the behavior can be modified by passing custom functions for
231/// `sort_spolys` and `abort_early_if`.
232/// 
233/// - `sort_spolys` should permute the given list of S-polynomials w.r.t. the given basis; this can be used to customize in which
234///   order S-polynomials are reduced, which can have huge impact on performance.
235///   Note that S-polynomials that are supposed to be reduced first should be put at the end of the list.
236/// - `abort_early_if` takes the current basis (unfortunately, currently with some additional information that can be ignored), and
237///   can return `true` to abort the GB computation, yielding the current basis. In this case, the basis will in general not be a GB,
238///   but can still be useful (e.g. `abort_early_if` might decide that a GB up to a fixed degree is sufficient).
239/// 
240/// # Explanation of logging output
241/// 
242/// If the passed computation controller accepts the logging, it will receive the following symbols:
243///  - `-` means an S-polynomial was reduced to zero
244///  - `s` means an S-polynomial reduced to a nonzero value and will be added to the basis at the next opportunity
245///  - `b(n)` means that the list of all generated basis polynomials has length `n`
246///  - `r(n)` means that the current basis of the ideal has length `n`
247///  - `S(n)` means that the algorithm still has to reduce `n` more S-polynomials
248///  - `f(n)` means that `n` S-polynomials have, in total, been discarded by using the Buchberger criteria
249///  - `{n}` means that the algorithm is currently reducing S-polynomials of degree `n`
250///  - `!` means that the algorithm decided to discard all current S-polynomial, and restart the computation with the current basis
251/// 
252#[stability::unstable(feature = "enable")]
253pub fn buchberger<P, O, Controller, SortFn, AbortFn>(ring: P, input_basis: Vec<El<P>>, order: O, mut sort_spolys: SortFn, mut abort_early_if: AbortFn, controller: Controller) -> Result<Vec<El<P>>, Controller::Abort>
254    where P: RingStore + Copy + Send + Sync,
255        El<P>: Send + Sync,
256        P::Type: MultivariatePolyRing,
257        <P::Type as RingExtension>::BaseRing: Sync,
258        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
259        O: MonomialOrder + Copy + Send + Sync,
260        PolyCoeff<P>: Send + Sync,
261        Controller: ComputationController,
262        SortFn: FnMut(&mut [SPoly], &[El<P>]),
263        AbortFn: FnMut(&[(El<P>, ExpandedMonomial)]) -> bool
264{
265    controller.run_computation(format_args!("buchberger(len={}, vars={})", input_basis.len(), ring.indeterminate_count()), |controller| {
266
267        // this are the basis polynomials we generated; we only append to this, such that the S-polys remain valid
268        let input_basis = inter_reduce(&ring, input_basis.into_iter().map(|f| augment_lm(ring, f, order)).collect(), order).into_iter().map(|(f, _)| f).collect::<Vec<_>>();
269        debug_assert!(input_basis.iter().all(|f| !ring.is_zero(f)));
270
271        let nilpotent_power = ring.base_ring().nilpotent_power().and_then(|e| if e != 0 { Some(e) } else { None });
272        assert!(nilpotent_power.is_none() || ring.base_ring().is_zero(&ring.base_ring().pow(ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()), nilpotent_power.unwrap())));
273
274        let sort_reducers = |reducers: &mut [(El<P>, ExpandedMonomial)]| {
275            // I have no idea why, but this order seems to give the best results
276            reducers.sort_by(|(lf, _), (rf, _)| order.compare(ring, &ring.LT(lf, order).unwrap().1, &ring.LT(rf, order).unwrap().1).then_with(|| ring.terms(lf).count().cmp(&ring.terms(rf).count())))
277        };
278
279        // invariant: `(reducers) = (basis)` and there exists a reduction to zero for every `f` in `basis` modulo `reducers`;
280        // reducers are always stored with an expanded version of their leading monomial, in order to simplify divisibility checks
281        let mut reducers: Vec<(El<P>, ExpandedMonomial)> = input_basis.iter().map(|f| augment_lm(ring, ring.clone_el(f), order)).collect::<Vec<_>>();
282        sort_reducers(&mut reducers);
283
284        let mut open = Vec::new();
285        let mut basis = Vec::new();
286        update_basis(ring, input_basis.into_iter(), &mut basis, &mut open, order, nilpotent_power, &mut 0, &mut sort_spolys);
287
288        let mut current_deg = 0;
289        let mut filtered_spolys = 0;
290        let mut changed = false;
291        loop {
292
293            // reduce all known S-polys of minimal lcm degree; in effect, this is the same as 
294            // the matrix reduction step during F4
295            let spolys_to_reduce_index = open.iter().enumerate().rev().filter(|(_, spoly)| ring.monomial_deg(&spoly.lcm_term(ring, &basis, order).1) > current_deg).next().map(|(i, _)| i + 1).unwrap_or(0);
296            let spolys_to_reduce = &open[spolys_to_reduce_index..];
297
298            let computation = ShortCircuitingComputation::new();
299            let new_polys = AppendOnlyVec::new();
300            let new_polys_ref = &new_polys;
301            let basis_ref = &basis;
302            let reducers_ref = &reducers;
303
304            computation.handle(controller.clone()).join_many(spolys_to_reduce.as_fn().map_fn(move |spoly| move |handle: ShortCircuitingComputationHandle<(), _>| {
305                let mut f = spoly.poly(ring, basis_ref, order);
306                
307                reduce_poly(ring, &mut f, || reducers_ref.iter().chain(new_polys_ref.iter()).map(|(f, lmf)| (f, lmf)), order);
308
309                if !ring.is_zero(&f) {
310                    log_progress!(handle, "s");
311                    _ = new_polys_ref.push(augment_lm(ring, f, order));
312                } else {
313                    log_progress!(handle, "-");
314                }
315
316                checkpoint!(handle);
317                return Ok(None);
318            }));
319
320            drop(open.drain(spolys_to_reduce_index..));
321            let new_polys = new_polys.into_vec();
322            _ = computation.finish()?;
323
324            // process the generated new polynomials
325            if new_polys.len() == 0 && open.len() == 0 {
326                if changed {
327                    log_progress!(controller, "!");
328                    // this seems necessary, as the invariants for `reducers` don't imply that it already is a GB;
329                    // more concretely, reducers contains polys of basis that are reduced with eath other, but the
330                    // S-polys between two of them might not have been considered
331                    return buchberger::<P, O, _, _, _>(ring, reducers.into_iter().map(|(f, _)| f).collect(), order, sort_spolys, abort_early_if, controller);
332                } else {
333                    return Ok(reducers.into_iter().map(|(f, _)| f).collect());
334                }
335            } else if new_polys.len() == 0 {
336                current_deg = ring.monomial_deg(&open.last().unwrap().lcm_term(ring, &basis, order).1);
337                log_progress!(controller, "{{{}}}", current_deg);
338            } else {
339                changed = true;
340                current_deg = 0;
341                update_basis(ring, new_polys.iter().map(|(f, _)| ring.clone_el(f)), &mut basis, &mut open, order, nilpotent_power, &mut filtered_spolys, &mut sort_spolys);
342                log_progress!(controller, "(b={})(S={})(f={})", basis.len(), open.len(), filtered_spolys);
343
344                reducers.extend(new_polys.into_iter());
345                reducers = inter_reduce(ring, reducers, order);
346                sort_reducers(&mut reducers);
347                log_progress!(controller, "(r={})", reducers.len());
348                if abort_early_if(&reducers) {
349                    log_progress!(controller, "(early_abort)");
350                    return Ok(reducers.into_iter().map(|(f, _)| f).collect());
351                }
352            }
353
354            // less S-polys if we restart from scratch with reducers
355            if open.len() + filtered_spolys > reducers.len() * reducers.len() / 2 + reducers.len() * nilpotent_power.unwrap_or(0) + 1 {
356                log_progress!(controller, "!");
357                return buchberger::<P, O, _, _, _>(ring, reducers.into_iter().map(|(f, _)| f).collect(), order, sort_spolys, abort_early_if, controller);
358            }
359        }
360    })
361}
362
363fn update_basis<I, P, O, SortFn>(ring: P, new_polys: I, basis: &mut Vec<El<P>>, open: &mut Vec<SPoly>, order: O, nilpotent_power: Option<usize>, filtered_spolys: &mut usize, sort_spolys: &mut SortFn)
364    where P: RingStore + Copy,
365        P::Type: MultivariatePolyRing,
366        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
367        O: MonomialOrder + Copy,
368        SortFn: FnMut(&mut [SPoly], &[El<P>]),
369        I: Iterator<Item = El<P>>
370{
371    for new_poly in new_polys {
372        basis.push(new_poly);
373        for i in 0..(basis.len() - 1) {
374            let spoly = SPoly::Standard(i, basis.len() - 1);
375            if filter_spoly(ring, spoly.clone(), &*basis, order).is_none() {
376                open.push(spoly);
377            } else {
378                *filtered_spolys += 1;
379            }
380        }
381        if let Some(e) = nilpotent_power {
382            for k in 1..e {
383                let spoly = SPoly::Nilpotent(basis.len() - 1, k);
384                if filter_spoly(ring, spoly.clone(), &*basis, order).is_none() {
385                    open.push(spoly);
386                } else {
387                    *filtered_spolys += 1;
388                }
389            }
390        }
391    }
392    sort_spolys(&mut *open, &*basis);
393}
394
395fn reduce_poly<'a, 'b, F, I, P, O>(ring: P, to_reduce: &mut El<P>, mut reducers: F, order: O)
396    where P: 'a + RingStore + Copy,
397        P::Type: MultivariatePolyRing,
398        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
399        O: MonomialOrder + Copy,
400        F: FnMut() -> I,
401        I: Iterator<Item = (&'a El<P>, &'b ExpandedMonomial)>
402{
403    while let Some((_, reducer, quo_c, quo_m)) = find_reducer(ring, to_reduce, reducers(), order) {
404        let prev_lm = ring.clone_monomial(&ring.LT(to_reduce, order).unwrap().1);
405        let mut scaled_reducer = ring.clone_el(reducer);
406        ring.mul_assign_monomial(&mut scaled_reducer, ring.clone_monomial(&quo_m));
407        ring.inclusion().mul_assign_ref_map(&mut scaled_reducer, &quo_c);
408        debug_assert!(order.compare(ring, ring.LT(&scaled_reducer, order).unwrap().1, ring.LT(&to_reduce, order).unwrap().1) == std::cmp::Ordering::Equal);
409        ring.sub_assign(to_reduce, scaled_reducer);
410        debug_assert!(ring.is_zero(&to_reduce) || order.compare(ring, &ring.LT(&to_reduce, order).unwrap().1, &prev_lm) == std::cmp::Ordering::Less);
411    }
412}
413
414#[stability::unstable(feature = "enable")]
415pub fn multivariate_division<'a, P, O, I>(ring: P, mut f: El<P>, reducers: I, order: O) -> El<P>
416    where P: 'a + RingStore + Copy,
417        P::Type: MultivariatePolyRing,
418        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
419        O: MonomialOrder + Copy,
420        I: Clone + Iterator<Item = &'a El<P>>
421{
422    let lms = reducers.clone().map(|f| ring.expand_monomial(ring.LT(f, order).unwrap().1)).collect::<Vec<_>>();
423    reduce_poly(ring, &mut f, || reducers.clone().zip(lms.iter()), order);
424    return f;
425}
426
427fn inter_reduce<P, O>(ring: P, mut polys: Vec<(El<P>, ExpandedMonomial)>, order: O) -> Vec<(El<P>, ExpandedMonomial)>
428    where P: RingStore + Copy,
429        P::Type: MultivariatePolyRing,
430        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
431        O: MonomialOrder + Copy
432{
433    let mut changed = true;
434    while changed {
435        changed = false;
436        let mut i = 0;
437        while i < polys.len() {
438            let last_i = polys.len() - 1;
439            polys.swap(i, last_i);
440            let (reducers, to_reduce) = polys.split_at_mut(last_i);
441            let to_reduce = &mut to_reduce[0];
442
443            reduce_poly(ring, &mut to_reduce.0, || reducers.iter().map(|(f, lmf)| (f, lmf)), order);
444
445            // undo swap so that the outer loop still iterates over every poly
446            if !ring.is_zero(&to_reduce.0) {
447                to_reduce.1 = ring.expand_monomial(ring.LT(&to_reduce.0, order).unwrap().1);
448                polys.swap(i, last_i);
449                i += 1;
450            } else {
451                _ = polys.pop().unwrap();
452            }
453        }
454    }
455    return polys;
456}
457
458use crate::rings::local::AsLocalPIR;
459use crate::rings::multivariate::multivariate_impl::MultivariatePolyRingImpl;
460
461///
462/// Computes a Groebner basis of the ideal generated by the input basis w.r.t. the given term ordering.
463/// 
464/// For a variant of this function that allows for more configuration, see [`buchberger()`].
465/// 
466pub fn buchberger_simple<P, O>(ring: P, input_basis: Vec<El<P>>, order: O) -> Vec<El<P>>
467    where P: RingStore + Copy + Send + Sync,
468        El<P>: Send + Sync,
469        P::Type: MultivariatePolyRing,
470        <P::Type as RingExtension>::BaseRing: Sync,
471        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field,
472        O: MonomialOrder + Copy + Send + Sync,
473        PolyCoeff<P>: Send + Sync
474{
475    let as_local_pir = AsLocalPIR::from_field(ring.base_ring());
476    let new_poly_ring = MultivariatePolyRingImpl::new(&as_local_pir, ring.indeterminate_count());
477    let from_ring = new_poly_ring.lifted_hom(ring, WrapHom::to_delegate_ring(as_local_pir.get_ring()));
478    let result = buchberger::<_, _, _, _, _>(
479        &new_poly_ring, 
480        input_basis.into_iter().map(|f| from_ring.map(f)).collect(), 
481        order, 
482        default_sort_fn(&new_poly_ring, order), 
483        |_| false, 
484        DontObserve
485    ).unwrap_or_else(no_error);
486    let to_ring = ring.lifted_hom(&new_poly_ring, UnwrapHom::from_delegate_ring(as_local_pir.get_ring()));
487    return result.into_iter().map(|f| to_ring.map(f)).collect();
488}
489
490#[cfg(test)]
491use crate::rings::poly::{dense_poly, PolyRingStore};
492#[cfg(test)]
493use crate::rings::zn::zn_static;
494#[cfg(test)]
495use crate::integer::BigIntRing;
496#[cfg(test)]
497use crate::rings::rational::RationalField;
498
499#[test]
500fn test_buchberger_small() {
501    let base = zn_static::F17;
502    let ring = MultivariatePolyRingImpl::new(base, 2);
503
504    let f1 = ring.from_terms([
505        (1, ring.create_monomial([2, 0])),
506        (1, ring.create_monomial([0, 2])),
507        (16, ring.create_monomial([0, 0]))
508    ].into_iter());
509    let f2 = ring.from_terms([
510        (1, ring.create_monomial([1, 1])),
511        (15, ring.create_monomial([0, 0]))
512    ].into_iter());
513
514    let actual = buchberger(&ring, vec![ring.clone_el(&f1), ring.clone_el(&f2)], DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_LOG_PROGRESS).unwrap_or_else(no_error);
515
516    let expected = ring.from_terms([
517        (16, ring.create_monomial([0, 3])),
518        (15, ring.create_monomial([1, 0])),
519        (1, ring.create_monomial([0, 1])),
520    ].into_iter());
521
522    assert_eq!(3, actual.len());
523    assert_el_eq!(ring, ring.zero(), multivariate_division(&ring, f1, actual.iter(), DegRevLex));
524    assert_el_eq!(ring, ring.zero(), multivariate_division(&ring, f2, actual.iter(), DegRevLex));
525    assert_el_eq!(ring, ring.zero(), multivariate_division(&ring, expected, actual.iter(), DegRevLex));
526}
527
528#[test]
529fn test_buchberger_larger() {
530    let base = zn_static::F17;
531    let ring = MultivariatePolyRingImpl::new(base, 3);
532
533    let f1 = ring.from_terms([
534        (1, ring.create_monomial([2, 1, 1])),
535        (1, ring.create_monomial([0, 2, 0])),
536        (1, ring.create_monomial([1, 0, 1])),
537        (2, ring.create_monomial([1, 0, 0])),
538        (1, ring.create_monomial([0, 0, 0]))
539    ].into_iter());
540    let f2 = ring.from_terms([
541        (1, ring.create_monomial([0, 3, 1])),
542        (1, ring.create_monomial([0, 0, 3])),
543        (1, ring.create_monomial([1, 1, 0]))
544    ].into_iter());
545    let f3 = ring.from_terms([
546        (1, ring.create_monomial([1, 0, 2])),
547        (1, ring.create_monomial([1, 0, 1])),
548        (2, ring.create_monomial([0, 1, 1])),
549        (7, ring.create_monomial([0, 0, 0]))
550    ].into_iter());
551
552    let actual = buchberger(&ring, vec![ring.clone_el(&f1), ring.clone_el(&f2), ring.clone_el(&f3)], DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_LOG_PROGRESS).unwrap_or_else(no_error);
553
554    let g1 = ring.from_terms([
555        (1, ring.create_monomial([0, 4, 0])),
556        (8, ring.create_monomial([0, 3, 1])),
557        (12, ring.create_monomial([0, 1, 3])),
558        (6, ring.create_monomial([0, 0, 4])),
559        (1, ring.create_monomial([0, 3, 0])),
560        (13, ring.create_monomial([0, 2, 1])),
561        (11, ring.create_monomial([0, 1, 2])),
562        (10, ring.create_monomial([0, 0, 3])),
563        (11, ring.create_monomial([0, 2, 0])),
564        (12, ring.create_monomial([0, 1, 1])),
565        (6, ring.create_monomial([0, 0, 2])),
566        (6, ring.create_monomial([0, 1, 0])),
567        (13, ring.create_monomial([0, 0, 1])),
568        (9, ring.create_monomial([0, 0, 0]))
569    ].into_iter());
570
571    assert_el_eq!(ring, ring.zero(), multivariate_division(&ring, g1, actual.iter(), DegRevLex));
572}
573
574#[test]
575fn test_generic_computation() {
576    let base = zn_static::F17;
577    let ring = MultivariatePolyRingImpl::new(base, 6);
578    let poly_ring = dense_poly::DensePolyRing::new(&ring, "X");
579
580    let var_i = |i: usize| ring.create_term(base.one(), ring.create_monomial((0..ring.indeterminate_count()).map(|j| if i == j { 1 } else { 0 })));
581    let X1 = poly_ring.mul(
582        poly_ring.from_terms([(var_i(0), 0), (ring.one(), 1)].into_iter()),
583        poly_ring.from_terms([(var_i(1), 0), (ring.one(), 1)].into_iter())
584    );
585    let X2 = poly_ring.mul(
586        poly_ring.add(poly_ring.clone_el(&X1), poly_ring.from_terms([(var_i(2), 0), (var_i(3), 1)].into_iter())),
587        poly_ring.add(poly_ring.clone_el(&X1), poly_ring.from_terms([(var_i(4), 0), (var_i(5), 1)].into_iter()))
588    );
589    let basis = vec![
590        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 0)),
591        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 1)),
592        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 2)),
593    ];
594
595    let start = std::time::Instant::now();
596    let gb1 = buchberger(&ring, basis.iter().map(|f| ring.clone_el(f)).collect(), DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_LOG_PROGRESS).unwrap_or_else(no_error);
597    let end = std::time::Instant::now();
598
599    println!("Computed GB in {} ms", (end - start).as_millis());
600
601    assert_eq!(11, gb1.len());
602}
603
604#[test]
605fn test_gb_local_ring() {
606    let base = AsLocalPIR::from_zn(zn_static::Zn::<16>::RING).unwrap();
607    let ring: MultivariatePolyRingImpl<_> = MultivariatePolyRingImpl::new(base, 1);
608    
609    let f = ring.from_terms([(base.int_hom().map(4), ring.create_monomial([1])), (base.one(), ring.create_monomial([0]))].into_iter());
610    let gb = buchberger(&ring, vec![f], DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_LOG_PROGRESS).unwrap_or_else(no_error);
611
612    assert_eq!(1, gb.len());
613    assert_el_eq!(ring, ring.one(), gb[0]);
614}
615
616#[test]
617fn test_gb_lex() {
618    let ZZ = BigIntRing::RING;
619    let QQ = AsLocalPIR::from_field(RationalField::new(ZZ));
620    let QQYX = MultivariatePolyRingImpl::new(&QQ, 2);
621    let [f, g] = QQYX.with_wrapped_indeterminates(|[Y, X]| [ 1 + X.pow_ref(2) + 2 * Y + (1 + X) * Y.pow_ref(2), 3 + X + (2 + X) * Y + (1 + X + X.pow_ref(2)) * Y.pow_ref(2) ]);
622    let expected = QQYX.with_wrapped_indeterminates(|[Y, X]| [ 
623        X.pow_ref(8) + 2 * X.pow_ref(7) + 3 * X.pow_ref(6) - 5 * X.pow_ref(5) - 10 * X.pow_ref(4) - 7 * X.pow_ref(3) + 8 * X.pow_ref(2) + 8 * X + 4,
624        2 * Y + X.pow_ref(6) + 3 * X.pow_ref(5) + 6 * X.pow_ref(4) + X.pow_ref(3) - 7 * X.pow_ref(2) - 12 * X - 2, 
625    ]);
626
627    let mut gb = buchberger_simple(&QQYX, vec![f, g], Lex);
628
629    assert_eq!(2, gb.len());
630    gb.sort_unstable_by_key(|f| QQYX.appearing_indeterminates(f).len());
631    for (mut f, mut e) in gb.into_iter().zip(expected.into_iter()) {
632        let f_lc_inv = QQ.invert(QQYX.LT(&f, Lex).unwrap().0).unwrap();
633        QQYX.inclusion().mul_assign_map(&mut f, f_lc_inv);
634        let e_lc_inv = QQ.invert(QQYX.LT(&e, Lex).unwrap().0).unwrap();
635        QQYX.inclusion().mul_assign_map(&mut e, e_lc_inv);
636        assert_el_eq!(QQYX, e, f);
637    }
638}
639
640#[cfg(test)]
641#[cfg(feature = "parallel")]
642static TEST_COMPUTATION_CONTROLLER: ExecuteMultithreaded<LogProgress> = RunMultithreadedLogProgress;
643#[cfg(test)]
644#[cfg(not(feature = "parallel"))]
645static TEST_COMPUTATION_CONTROLLER: LogProgress = TEST_LOG_PROGRESS;
646
647#[ignore]
648#[test]
649fn test_expensive_gb_1() {
650    let base = AsLocalPIR::from_zn(zn_static::Zn::<16>::RING).unwrap();
651    let ring: MultivariatePolyRingImpl<_> = MultivariatePolyRingImpl::new(base, 12);
652
653    let system = ring.with_wrapped_indeterminates(|[Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8, Y9, Y10, Y11]| [
654        Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y4.pow_ref(2) + 4 * Y0 * Y1 * Y2 * Y3 * Y4 * Y4 * Y8 + Y0 * Y1 * Y2 * Y5.pow_ref(2) * Y8.pow_ref(2) + Y0 * Y2 * Y3 * Y4 * Y6 + Y0 * Y1 * Y3 * Y4 * Y7 + Y0 * Y2 * Y5 * Y6 * Y8 + Y0 * Y1 * Y5 * Y7 * Y8 + Y0 * Y2 * Y3 * Y5 * Y10 + Y0 * Y1 * Y3 * Y5 * Y11 + Y0 * Y6 * Y7 + Y3 * Y5 * Y9 -  4,
655        2 * Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y4 * Y5 + 2 * Y0 * Y1 * Y2 * Y3 * Y5.pow_ref(2) * Y8 + Y0 * Y2 * Y3 * Y5 * Y6 + Y0 * Y1 * Y3 * Y5 * Y7 + 8,
656        Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y5.pow_ref(2) - 5
657    ]);
658
659    let part_of_result = ring.with_wrapped_indeterminates(|[_Y0, Y1, Y2, _Y3, _Y4, _Y5, Y6, Y7, _Y8, _Y9, _Y10, _Y11]| [
660        4 * Y2.pow_ref(2) * Y6.pow_ref(2) -  4 * Y1.pow_ref(2) * Y7.pow_ref(2),
661        8 * Y2 * Y6 + 8 * Y1 * Y7.clone()
662    ]);
663
664    let start = std::time::Instant::now();
665    let gb = buchberger(&ring, system.iter().map(|f| ring.clone_el(f)).collect(), DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_COMPUTATION_CONTROLLER).unwrap_or_else(no_error);
666    let end = std::time::Instant::now();
667
668    println!("Computed GB in {} ms", (end - start).as_millis());
669
670    for f in &part_of_result {
671        assert!(ring.is_zero(&multivariate_division(&ring, ring.clone_el(f), gb.iter(), DegRevLex)));
672    }
673
674    assert_eq!(108, gb.len());
675}
676
677#[test]
678#[ignore]
679fn test_expensive_gb_2() {
680    let base = zn_static::Fp::<7>::RING;
681    let ring = MultivariatePolyRingImpl::new(base, 7);
682
683    let basis = ring.with_wrapped_indeterminates_dyn(|[X0, X1, X2, X3, X4, X5, X6]| [
684        6 + 2 * X5 + 2 * X4 + X6 + 4 * X0 + 5 * X6 * X5 + X6 * X4 + 3 * X0 * X4 + 6 * X0 * X6 + 2 * X0 * X3 + X0 * X2 + 4 * X0 * X1 + 2 * X3 * X4 * X5 + 4 * X0 * X6 * X5 + 6 * X0 * X2 * X5 + 5 * X0 * X6 * X4 + 2 * X0 * X3 * X4 + 4 * X0 * X1 * X4 + X0 * X6.pow_ref(2) + 3 * X0 * X3 * X6 + 5 * X0 * X2 * X6 + 2 * X0 * X1 * X6 + X0 * X3.pow_ref(2) + 2 * X0 * X2 * X3 + 3 * X0 * X3 * X4 * X5 + 4 * X0 * X3 * X6 * X5 + 3 * X0 * X1 * X6 * X5 + 3 * X0 * X2 * X3 * X5 + 3 * X0 * X3 * X6 * X4 + 2 * X0 * X1 * X6 * X4 + 2 * X0 * X3.pow_ref(2) * X4 + 2 * X0 * X2 * X3 * X4 + 3 * X0 * X3.pow_ref(2) * X4 * X5 + 4 * X0 * X1 * X3 * X4 * X5 + X0 * X3.pow_ref(2) * X4.pow_ref(2),
685        5 + 4 * X0 + 6 * X4 * X5 + 3 * X6 * X5 + 4 * X0 * X4 + 3 * X0 * X6 + 6 * X0 * X3 + 6 * X0 * X2 + 6 * X6 * X4 * X5 + 2 * X0 * X4 * X5 + 4 * X0 * X6 * X5 + 3 * X0 * X2 * X5 + 3 * X0 * X6 * X4 + 5 * X0 * X3 * X4 + 6 * X0 * X2 * X4 + 4 * X0 * X6.pow_ref(2) + 3 * X0 * X3 * X6 + 3 * X0 * X2 * X6 + 2 * X0 * X6 * X4 * X5 + 6 * X0 * X3 * X4 * X5 + 5 * X0 * X1 * X4 * X5 + 6 * X0 * X6.pow_ref(2) * X5 + 2 * X0 * X3 * X6 * X5 + 2 * X0 * X2 * X6 * X5 + 6 * X0 * X1 * X6 * X5 + 6 * X0 * X2 * X3 * X5 + 6 * X0 * X3 * X4.pow_ref(2) + 4 * X0 * X6.pow_ref(2) * X4 + 6 * X0 * X3 * X6 * X4 + 3 * X0 * X2 * X6 * X4 + 4 * X0 * X3 * X6 * X4 * X5 + 5 * X0 * X1 * X6 * X4 * X5 + 6 * X0 * X3.pow_ref(2) * X4 * X5 + 5 * X0 * X2 * X3 * X4 * X5 + 3 * X0 * X3 * X6 * X4.pow_ref(2) + 6 * X0 * X3.pow_ref(2) * X4.pow_ref(2) * X5.clone(),
686        2 + 2 * X0 + 4 * X0 * X4 + 2 * X0 * X6 + 5 * X0 * X4 * X5 + 2 * X0 * X6 * X5 + 4 * X0 * X2 * X5 + 2 * X0 * X4.pow_ref(2) + 4 * X0 * X6 * X4 + 4 * X0 * X6.pow_ref(2) + 2 * X6 * X4 * X5.pow_ref(2) + 4 * X0 * X6 * X4 * X5 + X0 * X3 * X4 * X5 + X0 * X2 * X4 * X5 + 3 * X0 * X6.pow_ref(2) * X5 + 2 * X0 * X3 * X6 * X5 + 4 * X0 * X2 * X6 * X5 + 2 * X0 * X6 * X4.pow_ref(2) + X0 * X6.pow_ref(2) * X4 + 3 * X0 * X6 * X4 * X5.pow_ref(2) + 2 * X0 * X6.pow_ref(2) * X5.pow_ref(2) + 3 * X0 * X2 * X6 * X5.pow_ref(2) + X0 * X3 * X4.pow_ref(2) * X5 + X0 * X6.pow_ref(2) * X4 * X5 + X0 * X3 * X6 * X4 * X5 + 6 * X0 * X2 * X6 * X4 * X5 + 4 * X0 * X6.pow_ref(2) * X4.pow_ref(2) + 6 * X0 * X3 * X6 * X4 * X5.pow_ref(2) + 4 * X0 * X1 * X6 * X4 * X5.pow_ref(2) + 4 * X0 * X2 * X3 * X4 * X5.pow_ref(2) + 6 * X0 * X3 * X6 * X4.pow_ref(2) * X5 + 2 * X0 * X3.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(2),
687        4 + 5 * X0 * X4 * X5 + 6 * X0 * X6 * X5 + 5 * X0 * X4.pow_ref(2) * X5 + 3 * X0 * X6 * X4 * X5 + 3 * X0 * X6.pow_ref(2) * X5 + 6 * X0 * X6 * X4 * X5.pow_ref(2) + 5 * X0 * X2 * X4 * X5.pow_ref(2) + X0 * X6.pow_ref(2) * X5.pow_ref(2) + 6 * X0 * X2 * X6 * X5.pow_ref(2) + 4 * X0 * X6 * X4.pow_ref(2) * X5 + 2 * X0 * X6.pow_ref(2) * X4 * X5 + 5 * X0 * X3 * X4.pow_ref(2) * X5.pow_ref(2) + 3 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(2) + 5 * X0 * X3 * X6 * X4 * X5.pow_ref(2) + 4 * X0 * X2 * X6 * X4 * X5.pow_ref(2) + 6 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5 + 4 * X0 * X3 * X6 * X4.pow_ref(2) * X5.pow_ref(2),
688        4 + 4 * X0 * X4.pow_ref(2) * X5.pow_ref(2) + X0 * X6 * X4 * X5.pow_ref(2) + X0 * X6.pow_ref(2) * X5.pow_ref(2) + 5 * X0 * X6 * X4.pow_ref(2) * X5.pow_ref(2) + 6 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(2) + 3 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(3) + 4 * X0 * X2 * X6 * X4 * X5.pow_ref(3) + 6 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(2) + 4 * X0 * X3 * X6 * X4.pow_ref(2) * X5.pow_ref(3),
689        5 * X0 * X6 * X4.pow_ref(2) * X5.pow_ref(3) + 6 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(3) + 5 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(3),
690        2 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(4)
691    ]);
692
693    let start = std::time::Instant::now();
694    let gb = buchberger(&ring, basis, DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_COMPUTATION_CONTROLLER).unwrap_or_else(no_error);
695    let end = std::time::Instant::now();
696
697    println!("Computed GB in {} ms", (end - start).as_millis());
698
699    assert_eq!(130, gb.len());
700}
701
702#[test]
703#[ignore]
704fn test_groebner_cyclic6() {
705    let base = zn_static::Fp::<65537>::RING;
706    let ring = MultivariatePolyRingImpl::new(base, 6);
707
708    let cyclic6 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, t, u, v]| {
709        [x + y + z + t + u + v, x*y + y*z + z*t + t*u + x*v + u*v, x*y*z + y*z*t + z*t*u + x*y*v + x*u*v + t*u*v, x*y*z*t + y*z*t*u + x*y*z*v + x*y*u*v + x*t*u*v + z*t*u*v, x*y*z*t*u + x*y*z*t*v + x*y*z*u*v + x*y*t*u*v + x*z*t*u*v + y*z*t*u*v, x*y*z*t*u*v - 1]
710    });
711
712    let start = std::time::Instant::now();
713    let gb = buchberger(&ring, cyclic6, DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_COMPUTATION_CONTROLLER).unwrap_or_else(no_error);
714    let end = std::time::Instant::now();
715
716    println!("Computed GB in {} ms", (end - start).as_millis());
717    assert_eq!(45, gb.len());
718
719}
720
721#[test]
722#[ignore]
723fn test_groebner_cyclic7() {
724    let base = zn_static::Fp::<65537>::RING;
725    let ring = MultivariatePolyRingImpl::new(base, 7);
726
727    let cyclic7 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, t, u, v, w]| [
728        x + y + z + t + u + v + w, x*y + y*z + z*t + t*u + u*v + x*w + v*w, x*y*z + y*z*t + z*t*u + t*u*v + x*y*w + x*v*w + u*v*w, x*y*z*t + y*z*t*u + z*t*u*v + x*y*z*w + x*y*v*w + x*u*v*w + t*u*v*w, 
729        x*y*z*t*u + y*z*t*u*v + x*y*z*t*w + x*y*z*v*w + x*y*u*v*w + x*t*u*v*w + z*t*u*v*w, x*y*z*t*u*v + x*y*z*t*u*w + x*y*z*t*v*w + x*y*z*u*v*w + x*y*t*u*v*w + x*z*t*u*v*w + y*z*t*u*v*w, x*y*z*t*u*v*w - 1
730    ]);
731
732    let start = std::time::Instant::now();
733    let gb = buchberger(&ring, cyclic7, DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_COMPUTATION_CONTROLLER).unwrap_or_else(no_error);
734    let end = std::time::Instant::now();
735
736    println!("Computed GB in {} ms", (end - start).as_millis());
737    assert_eq!(209, gb.len());
738}
739
740#[test]
741#[ignore]
742fn test_groebner_cyclic8() {
743    let base = zn_static::Fp::<65537>::RING;
744    let ring = MultivariatePolyRingImpl::new(base, 8);
745
746    let cyclic7 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, s, t, u, v, w]| [
747        x + y + z + s + t + u + v + w, x*y + y*z + z*s + s*t + t*u + u*v + x*w + v*w, x*y*z + y*z*s + z*s*t + s*t*u + t*u*v + x*y*w + x*v*w + u*v*w, 
748        x*y*z*s + y*z*s*t + z*s*t*u + s*t*u*v + x*y*z*w + x*y*v*w + x*u*v*w + t*u*v*w, x*y*z*s*t + y*z*s*t*u + z*s*t*u*v + x*y*z*s*w + x*y*z*v*w + x*y*u*v*w + x*t*u*v*w + s*t*u*v*w, x*y*z*s*t*u + y*z*s*t*u*v + x*y*z*s*t*w + x*y*z*s*v*w + x*y*z*u*v*w + x*y*t*u*v*w + x*s*t*u*v*w + z*s*t*u*v*w, 
749        x*y*z*s*t*u*v + x*y*z*s*t*u*w + x*y*z*s*t*v*w + x*y*z*s*u*v*w + x*y*z*t*u*v*w + x*y*s*t*u*v*w + x*z*s*t*u*v*w + y*z*s*t*u*v*w, x*y*z*s*t*u*v*w - 1
750    ]);
751
752    let start = std::time::Instant::now();
753    let gb = buchberger(&ring, cyclic7, DegRevLex, default_sort_fn(&ring, DegRevLex), |_| false, TEST_COMPUTATION_CONTROLLER).unwrap_or_else(no_error);
754    let end = std::time::Instant::now();
755
756    println!("Computed GB in {} ms", (end - start).as_millis());
757    assert_eq!(372, gb.len());
758}