Skip to main content

feanor_math/algorithms/
buchberger.rs

1use std::cmp::min;
2use std::fmt::Debug;
3
4use append_only_vec::AppendOnlyVec;
5
6use crate::computation::*;
7use crate::delegate::{UnwrapHom, WrapHom};
8use crate::divisibility::{DivisibilityRing, DivisibilityRingStore};
9use crate::field::Field;
10use crate::homomorphism::Homomorphism;
11use crate::local::{PrincipalLocalRing, PrincipalLocalRingStore};
12use crate::pid::PrincipalIdealRingStore;
13use crate::ring::*;
14use crate::rings::multivariate::*;
15use crate::seq::*;
16
17#[stability::unstable(feature = "enable")]
18#[derive(PartialEq, Clone, Eq, Hash)]
19pub enum SPoly {
20    Standard(usize, usize),
21    Nilpotent(
22        // poly index
23        usize,
24        // power-of-p multiplier
25        usize,
26    ),
27}
28
29impl Debug for SPoly {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            SPoly::Standard(i, j) => write!(f, "S({}, {})", i, j),
33            SPoly::Nilpotent(i, k) => write!(f, "p^{} F({})", k, i),
34        }
35    }
36}
37
38fn term_xlcm<P>(
39    ring: P,
40    (l_c, l_m): (&PolyCoeff<P>, &PolyMonomial<P>),
41    (r_c, r_m): (&PolyCoeff<P>, &PolyMonomial<P>),
42) -> (
43    (PolyCoeff<P>, PolyMonomial<P>),
44    (PolyCoeff<P>, PolyMonomial<P>),
45    (PolyCoeff<P>, PolyMonomial<P>),
46)
47where
48    P: RingStore,
49    P::Type: MultivariatePolyRing,
50    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
51{
52    let d_c = ring.base_ring().ideal_gen(l_c, r_c);
53    let m_m = ring.monomial_lcm(ring.clone_monomial(l_m), r_m);
54    let l_factor = ring.base_ring().checked_div(r_c, &d_c).unwrap();
55    let r_factor = ring.base_ring().checked_div(l_c, &d_c).unwrap();
56    let m_c = ring
57        .base_ring()
58        .mul_ref_snd(ring.base_ring().mul_ref_snd(d_c, &r_factor), &l_factor);
59    return (
60        (
61            l_factor,
62            ring.monomial_div(ring.clone_monomial(&m_m), l_m).ok().unwrap(),
63        ),
64        (
65            r_factor,
66            ring.monomial_div(ring.clone_monomial(&m_m), r_m).ok().unwrap(),
67        ),
68        (m_c, m_m),
69    );
70}
71
72fn term_lcm<P>(
73    ring: P,
74    (l_c, l_m): (&PolyCoeff<P>, &PolyMonomial<P>),
75    (r_c, r_m): (&PolyCoeff<P>, &PolyMonomial<P>),
76) -> (PolyCoeff<P>, PolyMonomial<P>)
77where
78    P: RingStore,
79    P::Type: MultivariatePolyRing,
80    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
81{
82    let d_c = ring.base_ring().ideal_gen(l_c, r_c);
83    let m_m = ring.monomial_lcm(ring.clone_monomial(l_m), r_m);
84    let l_factor = ring.base_ring().checked_div(r_c, &d_c).unwrap();
85    let r_factor = ring.base_ring().checked_div(l_c, &d_c).unwrap();
86    let m_c = ring
87        .base_ring()
88        .mul_ref_snd(ring.base_ring().mul_ref_snd(d_c, &r_factor), &l_factor);
89    return (m_c, m_m);
90}
91
92impl SPoly {
93    #[stability::unstable(feature = "enable")]
94    pub fn lcm_term<P, O>(&self, ring: P, basis: &[El<P>], order: O) -> (PolyCoeff<P>, PolyMonomial<P>)
95    where
96        P: RingStore + Copy,
97        P::Type: MultivariatePolyRing,
98        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
99        O: MonomialOrder + Copy,
100    {
101        match self {
102            SPoly::Standard(i, j) => term_lcm(
103                &ring,
104                ring.LT(&basis[*i], order).unwrap(),
105                ring.LT(&basis[*j], order).unwrap(),
106            ),
107            Self::Nilpotent(i, k) => {
108                let (c, m) = ring.LT(&basis[*i], order).unwrap();
109                (
110                    ring.base_ring().mul_ref_fst(
111                        c,
112                        ring.base_ring()
113                            .pow(ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()), *k),
114                    ),
115                    ring.clone_monomial(m),
116                )
117            }
118        }
119    }
120
121    #[stability::unstable(feature = "enable")]
122    pub fn poly<P, O>(&self, ring: P, basis: &[El<P>], order: O) -> El<P>
123    where
124        P: RingStore + Copy,
125        P::Type: MultivariatePolyRing,
126        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
127        O: MonomialOrder + Copy,
128    {
129        match self {
130            SPoly::Standard(i, j) => {
131                let (f1_factor, f2_factor, _) = term_xlcm(
132                    &ring,
133                    ring.LT(&basis[*i], order).unwrap(),
134                    ring.LT(&basis[*j], order).unwrap(),
135                );
136                let mut f1_scaled = ring.clone_el(&basis[*i]);
137                ring.mul_assign_monomial(&mut f1_scaled, f1_factor.1);
138                ring.inclusion().mul_assign_map(&mut f1_scaled, f1_factor.0);
139                let mut f2_scaled = ring.clone_el(&basis[*j]);
140                ring.mul_assign_monomial(&mut f2_scaled, f2_factor.1);
141                ring.inclusion().mul_assign_map(&mut f2_scaled, f2_factor.0);
142                return ring.sub(f1_scaled, f2_scaled);
143            }
144            SPoly::Nilpotent(i, k) => {
145                let mut result = ring.clone_el(&basis[*i]);
146                ring.inclusion().mul_assign_map(
147                    &mut result,
148                    ring.base_ring()
149                        .pow(ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()), *k),
150                );
151                return result;
152            }
153        }
154    }
155}
156
157#[inline(never)]
158fn find_reducer<'a, 'b, P, O, I>(
159    ring: P,
160    f: &El<P>,
161    reducers: I,
162    order: O,
163) -> Option<(usize, &'a El<P>, PolyCoeff<P>, PolyMonomial<P>)>
164where
165    P: RingStore + Copy,
166    P::Type: MultivariatePolyRing,
167    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
168    O: MonomialOrder + Copy,
169    I: Iterator<Item = (&'a El<P>, &'b ExpandedMonomial)>,
170{
171    if ring.is_zero(f) {
172        return None;
173    }
174    let (f_lc, f_lm) = ring.LT(f, order).unwrap();
175    let f_lm_expanded = ring.expand_monomial(f_lm);
176    reducers
177        .enumerate()
178        .filter_map(|(i, (reducer, reducer_lm_expanded))| {
179            if (0..ring.indeterminate_count()).all(|j| reducer_lm_expanded[j] <= f_lm_expanded[j]) {
180                let (r_lc, r_lm) = ring.LT(reducer, order).unwrap();
181                let quo_m = ring.monomial_div(ring.clone_monomial(f_lm), r_lm).ok().unwrap();
182                if let Some(quo_c) = ring.base_ring().checked_div(f_lc, r_lc) {
183                    return Some((i, reducer, quo_c, quo_m));
184                }
185            }
186            return None;
187        })
188        .next()
189}
190
191#[inline(never)]
192fn filter_spoly<P, O>(ring: P, new_spoly: SPoly, basis: &[El<P>], order: O) -> Option<usize>
193where
194    P: RingStore + Copy,
195    P::Type: MultivariatePolyRing,
196    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
197    O: MonomialOrder + Copy,
198{
199    match new_spoly {
200        SPoly::Standard(i, k) => {
201            assert!(i < k);
202            let (bi_c, bi_m) = ring.LT(&basis[i], order).unwrap();
203            let (bk_c, bk_m) = ring.LT(&basis[k], order).unwrap();
204            let (S_c, S_m) = term_lcm(ring, (bi_c, bi_m), (bk_c, bk_m));
205            let S_c_val = ring.base_ring().valuation(&S_c).unwrap();
206
207            if S_c_val == 0
208                && order.eq_mon(
209                    ring,
210                    &ring.monomial_div(ring.clone_monomial(&S_m), bi_m).ok().unwrap(),
211                    bk_m,
212                )
213            {
214                return Some(usize::MAX);
215            }
216
217            (0..k)
218                .filter_map(|j| {
219                    if j == i {
220                        return None;
221                    }
222                    // more experiments needed - for some weird reason, replacing "properly divides"
223                    // with "divides" (assuming I didn't make a mistake) leads
224                    // to terrible performance
225                    let (bj_c, bj_m) = ring.LT(&basis[j], order).unwrap();
226                    let (f_c, f_m) = term_lcm(ring, (bj_c, bj_m), (bk_c, bk_m));
227                    let f_c_val = ring.base_ring().valuation(&f_c).unwrap();
228
229                    if j < i && order.eq_mon(ring, &f_m, &S_m) && f_c_val <= S_c_val {
230                        return Some(j);
231                    }
232                    if let Ok(quo) = ring.monomial_div(ring.clone_monomial(&S_m), &f_m)
233                        && f_c_val <= S_c_val
234                        && (f_c_val < S_c_val || ring.monomial_deg(&quo) > 0)
235                    {
236                        return Some(j);
237                    }
238                    return None;
239                })
240                .next()
241        }
242        SPoly::Nilpotent(i, k) => {
243            let nilpotent_power = ring.base_ring().nilpotent_power().unwrap();
244            let f = &basis[i];
245
246            let mut smallest_elim_coeff_valuation = usize::MAX;
247            let mut current = ring.LT(f, order).unwrap();
248            while ring.base_ring().valuation(current.0).unwrap() + k >= nilpotent_power {
249                smallest_elim_coeff_valuation = min(
250                    smallest_elim_coeff_valuation,
251                    ring.base_ring().valuation(current.0).unwrap(),
252                );
253                let next = ring.largest_term_lt(f, order, current.1);
254                if next.is_none() {
255                    return Some(usize::MAX);
256                }
257                current = next.unwrap();
258            }
259            assert!(
260                smallest_elim_coeff_valuation == usize::MAX || smallest_elim_coeff_valuation + k >= nilpotent_power
261            );
262            if smallest_elim_coeff_valuation == usize::MAX || smallest_elim_coeff_valuation + k > nilpotent_power {
263                return Some(usize::MAX);
264            } else {
265                return None;
266            }
267        }
268    }
269}
270
271#[stability::unstable(feature = "enable")]
272pub fn default_sort_fn<P, O>(ring: P, order: O) -> impl FnMut(&mut [SPoly], &[El<P>])
273where
274    P: RingStore + Copy,
275    P::Type: MultivariatePolyRing,
276    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
277    O: MonomialOrder + Copy,
278{
279    move |open, basis| {
280        open.sort_by_key(|spoly| {
281            let (lc, lm) = spoly.lcm_term(ring, basis, order);
282            (
283                -(ring.base_ring().valuation(&lc).unwrap_or(0) as i64),
284                -(ring.monomial_deg(&lm) as i64),
285            )
286        })
287    }
288}
289
290#[stability::unstable(feature = "enable")]
291pub type ExpandedMonomial = Vec<usize>;
292
293fn augment_lm<P, O>(ring: P, f: El<P>, order: O) -> (El<P>, ExpandedMonomial)
294where
295    P: RingStore + Copy,
296    P::Type: MultivariatePolyRing,
297    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
298    O: MonomialOrder,
299{
300    let exponents = ring.expand_monomial(ring.LT(&f, order).unwrap().1);
301    return (f, exponents);
302}
303
304/// Computes a Groebner basis of the ideal generated by the input basis w.r.t. the given term
305/// ordering.
306///
307/// For a variant of this function that uses sensible defaults for most parameters, see
308/// [`buchberger_simple()`].
309///
310/// The algorithm proceeds F4-style, i.e. reduces multiple S-polynomials before adding them to the
311/// basis. When using a fast polynomial ring implementation (e.g.
312/// [`crate::rings::multivariate::multivariate_impl::MultivariatePolyRingImpl`]), this makes the
313/// algorithm as efficient as standard F4. Furthermore, the behavior can be modified by passing
314/// custom functions for `sort_spolys` and `abort_early_if`.
315///
316/// - `sort_spolys` should permute the given list of S-polynomials w.r.t. the given basis; this can
317///   be used to customize in which order S-polynomials are reduced, which can have huge impact on
318///   performance. Note that S-polynomials that are supposed to be reduced first should be put at
319///   the end of the list.
320/// - `abort_early_if` takes the current basis (unfortunately, currently with some additional
321///   information that can be ignored), and can return `true` to abort the GB computation, yielding
322///   the current basis. In this case, the basis will in general not be a GB, but can still be
323///   useful (e.g. `abort_early_if` might decide that a GB up to a fixed degree is sufficient).
324///
325/// # Explanation of logging output
326///
327/// If the passed computation controller accepts the logging, it will receive the following symbols:
328///  - `-` means an S-polynomial was reduced to zero
329///  - `s` means an S-polynomial reduced to a nonzero value and will be added to the basis at the
330///    next opportunity
331///  - `b(n)` means that the list of all generated basis polynomials has length `n`
332///  - `r(n)` means that the current basis of the ideal has length `n`
333///  - `S(n)` means that the algorithm still has to reduce `n` more S-polynomials
334///  - `f(n)` means that `n` S-polynomials have, in total, been discarded by using the Buchberger
335///    criteria
336///  - `{n}` means that the algorithm is currently reducing S-polynomials of degree `n`
337///  - `!` means that the algorithm decided to discard all current S-polynomial, and restart the
338///    computation with the current basis
339#[stability::unstable(feature = "enable")]
340pub fn buchberger<P, O, Controller, SortFn, AbortFn>(
341    ring: P,
342    input_basis: Vec<El<P>>,
343    order: O,
344    mut sort_spolys: SortFn,
345    mut abort_early_if: AbortFn,
346    controller: Controller,
347) -> Result<Vec<El<P>>, Controller::Abort>
348where
349    P: RingStore + Copy + Send + Sync,
350    El<P>: Send + Sync,
351    P::Type: MultivariatePolyRing,
352    <P::Type as RingExtension>::BaseRing: Sync,
353    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
354    O: MonomialOrder + Copy + Send + Sync,
355    PolyCoeff<P>: Send + Sync,
356    Controller: ComputationController,
357    SortFn: FnMut(&mut [SPoly], &[El<P>]),
358    AbortFn: FnMut(&[(El<P>, ExpandedMonomial)]) -> bool,
359{
360    controller.run_computation(
361        format_args!(
362            "buchberger(len={}, vars={})",
363            input_basis.len(),
364            ring.indeterminate_count()
365        ),
366        |controller| {
367            // this are the basis polynomials we generated; we only append to this, such that the
368            // S-polys remain valid
369            let input_basis = inter_reduce(
370                &ring,
371                input_basis.into_iter().map(|f| augment_lm(ring, f, order)).collect(),
372                order,
373            )
374            .into_iter()
375            .map(|(f, _)| f)
376            .collect::<Vec<_>>();
377            debug_assert!(input_basis.iter().all(|f| !ring.is_zero(f)));
378
379            let nilpotent_power = ring
380                .base_ring()
381                .nilpotent_power()
382                .and_then(|e| if e != 0 { Some(e) } else { None });
383            assert!(
384                nilpotent_power.is_none()
385                    || ring.base_ring().is_zero(&ring.base_ring().pow(
386                        ring.base_ring().clone_el(ring.base_ring().max_ideal_gen()),
387                        nilpotent_power.unwrap()
388                    ))
389            );
390
391            let sort_reducers = |reducers: &mut [(El<P>, ExpandedMonomial)]| {
392                // I have no idea why, but this order seems to give the best results
393                reducers.sort_by(|(lf, _), (rf, _)| {
394                    order
395                        .compare(ring, ring.LT(lf, order).unwrap().1, ring.LT(rf, order).unwrap().1)
396                        .then_with(|| ring.terms(lf).count().cmp(&ring.terms(rf).count()))
397                })
398            };
399
400            // invariant: `(reducers) = (basis)` and there exists a reduction to zero for every `f`
401            // in `basis` modulo `reducers`; reducers are always stored with an expanded
402            // version of their leading monomial, in order to simplify divisibility checks
403            let mut reducers: Vec<(El<P>, ExpandedMonomial)> = input_basis
404                .iter()
405                .map(|f| augment_lm(ring, ring.clone_el(f), order))
406                .collect::<Vec<_>>();
407            sort_reducers(&mut reducers);
408
409            let mut open = Vec::new();
410            let mut basis = Vec::new();
411            update_basis(
412                ring,
413                input_basis.into_iter(),
414                &mut basis,
415                &mut open,
416                order,
417                nilpotent_power,
418                &mut 0,
419                &mut sort_spolys,
420            );
421
422            let mut current_deg = 0;
423            let mut filtered_spolys = 0;
424            let mut changed = false;
425            loop {
426                // reduce all known S-polys of minimal lcm degree; in effect, this is the same as
427                // the matrix reduction step during F4
428                let spolys_to_reduce_index = open
429                    .iter()
430                    .enumerate()
431                    .rev()
432                    .find(|(_, spoly)| ring.monomial_deg(&spoly.lcm_term(ring, &basis, order).1) > current_deg)
433                    .map(|(i, _)| i + 1)
434                    .unwrap_or(0);
435                let spolys_to_reduce = &open[spolys_to_reduce_index..];
436
437                let computation = ShortCircuitingComputation::new();
438                let new_polys = AppendOnlyVec::new();
439                let new_polys_ref = &new_polys;
440                let basis_ref = &basis;
441                let reducers_ref = &reducers;
442
443                computation
444                    .handle(controller.clone())
445                    .join_many(spolys_to_reduce.as_fn().map_fn(move |spoly| {
446                        move |handle: ShortCircuitingComputationHandle<(), _>| {
447                            let mut f = spoly.poly(ring, basis_ref, order);
448
449                            reduce_poly(
450                                ring,
451                                &mut f,
452                                || reducers_ref.iter().chain(new_polys_ref.iter()).map(|(f, lmf)| (f, lmf)),
453                                order,
454                            );
455
456                            if !ring.is_zero(&f) {
457                                log_progress!(handle, "s");
458                                _ = new_polys_ref.push(augment_lm(ring, f, order));
459                            } else {
460                                log_progress!(handle, "-");
461                            }
462
463                            checkpoint!(handle);
464                            return Ok(None);
465                        }
466                    }));
467
468                drop(open.drain(spolys_to_reduce_index..));
469                let new_polys = new_polys.into_vec();
470                _ = computation.finish()?;
471
472                // process the generated new polynomials
473                if new_polys.is_empty() && open.is_empty() {
474                    if changed {
475                        log_progress!(controller, "!");
476                        // this seems necessary, as the invariants for `reducers` don't imply that
477                        // it already is a GB; more concretely, reducers
478                        // contains polys of basis that are reduced with eath other, but the
479                        // S-polys between two of them might not have been considered
480                        return buchberger::<P, O, _, _, _>(
481                            ring,
482                            reducers.into_iter().map(|(f, _)| f).collect(),
483                            order,
484                            sort_spolys,
485                            abort_early_if,
486                            controller,
487                        );
488                    } else {
489                        return Ok(reducers.into_iter().map(|(f, _)| f).collect());
490                    }
491                } else if new_polys.is_empty() {
492                    current_deg = ring.monomial_deg(&open.last().unwrap().lcm_term(ring, &basis, order).1);
493                    log_progress!(controller, "{{{}}}", current_deg);
494                } else {
495                    changed = true;
496                    current_deg = 0;
497                    update_basis(
498                        ring,
499                        new_polys.iter().map(|(f, _)| ring.clone_el(f)),
500                        &mut basis,
501                        &mut open,
502                        order,
503                        nilpotent_power,
504                        &mut filtered_spolys,
505                        &mut sort_spolys,
506                    );
507                    log_progress!(
508                        controller,
509                        "(b={})(S={})(f={})",
510                        basis.len(),
511                        open.len(),
512                        filtered_spolys
513                    );
514
515                    reducers.extend(new_polys);
516                    reducers = inter_reduce(ring, reducers, order);
517                    sort_reducers(&mut reducers);
518                    log_progress!(controller, "(r={})", reducers.len());
519                    if abort_early_if(&reducers) {
520                        log_progress!(controller, "(early_abort)");
521                        return Ok(reducers.into_iter().map(|(f, _)| f).collect());
522                    }
523                }
524
525                // less S-polys if we restart from scratch with reducers
526                if open.len() + filtered_spolys
527                    > reducers.len() * reducers.len() / 2 + reducers.len() * nilpotent_power.unwrap_or(0) + 1
528                {
529                    log_progress!(controller, "!");
530                    return buchberger::<P, O, _, _, _>(
531                        ring,
532                        reducers.into_iter().map(|(f, _)| f).collect(),
533                        order,
534                        sort_spolys,
535                        abort_early_if,
536                        controller,
537                    );
538                }
539            }
540        },
541    )
542}
543
544fn update_basis<I, P, O, SortFn>(
545    ring: P,
546    new_polys: I,
547    basis: &mut Vec<El<P>>,
548    open: &mut Vec<SPoly>,
549    order: O,
550    nilpotent_power: Option<usize>,
551    filtered_spolys: &mut usize,
552    sort_spolys: &mut SortFn,
553) where
554    P: RingStore + Copy,
555    P::Type: MultivariatePolyRing,
556    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing,
557    O: MonomialOrder + Copy,
558    SortFn: FnMut(&mut [SPoly], &[El<P>]),
559    I: Iterator<Item = El<P>>,
560{
561    for new_poly in new_polys {
562        basis.push(new_poly);
563        for i in 0..(basis.len() - 1) {
564            let spoly = SPoly::Standard(i, basis.len() - 1);
565            if filter_spoly(ring, spoly.clone(), &*basis, order).is_none() {
566                open.push(spoly);
567            } else {
568                *filtered_spolys += 1;
569            }
570        }
571        if let Some(e) = nilpotent_power {
572            for k in 1..e {
573                let spoly = SPoly::Nilpotent(basis.len() - 1, k);
574                if filter_spoly(ring, spoly.clone(), &*basis, order).is_none() {
575                    open.push(spoly);
576                } else {
577                    *filtered_spolys += 1;
578                }
579            }
580        }
581    }
582    sort_spolys(&mut *open, &*basis);
583}
584
585fn reduce_poly<'a, 'b, F, I, P, O>(ring: P, to_reduce: &mut El<P>, mut reducers: F, order: O)
586where
587    P: 'a + RingStore + Copy,
588    P::Type: MultivariatePolyRing,
589    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
590    O: MonomialOrder + Copy,
591    F: FnMut() -> I,
592    I: Iterator<Item = (&'a El<P>, &'b ExpandedMonomial)>,
593{
594    while let Some((_, reducer, quo_c, quo_m)) = find_reducer(ring, to_reduce, reducers(), order) {
595        let prev_lm = ring.clone_monomial(ring.LT(to_reduce, order).unwrap().1);
596        let mut scaled_reducer = ring.clone_el(reducer);
597        ring.mul_assign_monomial(&mut scaled_reducer, ring.clone_monomial(&quo_m));
598        ring.inclusion().mul_assign_ref_map(&mut scaled_reducer, &quo_c);
599        debug_assert!(
600            order.compare(
601                ring,
602                ring.LT(&scaled_reducer, order).unwrap().1,
603                ring.LT(to_reduce, order).unwrap().1
604            ) == std::cmp::Ordering::Equal
605        );
606        ring.sub_assign(to_reduce, scaled_reducer);
607        debug_assert!(
608            ring.is_zero(to_reduce)
609                || order.compare(ring, ring.LT(to_reduce, order).unwrap().1, &prev_lm) == std::cmp::Ordering::Less
610        );
611    }
612}
613
614#[stability::unstable(feature = "enable")]
615pub fn multivariate_division<'a, P, O, I>(ring: P, mut f: El<P>, reducers: I, order: O) -> El<P>
616where
617    P: 'a + RingStore + Copy,
618    P::Type: MultivariatePolyRing,
619    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
620    O: MonomialOrder + Copy,
621    I: Clone + Iterator<Item = &'a El<P>>,
622{
623    let lms = reducers
624        .clone()
625        .map(|f| ring.expand_monomial(ring.LT(f, order).unwrap().1))
626        .collect::<Vec<_>>();
627    reduce_poly(ring, &mut f, || reducers.clone().zip(lms.iter()), order);
628    return f;
629}
630
631fn inter_reduce<P, O>(ring: P, mut polys: Vec<(El<P>, ExpandedMonomial)>, order: O) -> Vec<(El<P>, ExpandedMonomial)>
632where
633    P: RingStore + Copy,
634    P::Type: MultivariatePolyRing,
635    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: DivisibilityRing,
636    O: MonomialOrder + Copy,
637{
638    let mut changed = true;
639    while changed {
640        changed = false;
641        let mut i = 0;
642        while i < polys.len() {
643            let last_i = polys.len() - 1;
644            polys.swap(i, last_i);
645            let (reducers, to_reduce) = polys.split_at_mut(last_i);
646            let to_reduce = &mut to_reduce[0];
647
648            reduce_poly(
649                ring,
650                &mut to_reduce.0,
651                || reducers.iter().map(|(f, lmf)| (f, lmf)),
652                order,
653            );
654
655            // undo swap so that the outer loop still iterates over every poly
656            if !ring.is_zero(&to_reduce.0) {
657                to_reduce.1 = ring.expand_monomial(ring.LT(&to_reduce.0, order).unwrap().1);
658                polys.swap(i, last_i);
659                i += 1;
660            } else {
661                _ = polys.pop().unwrap();
662            }
663        }
664    }
665    return polys;
666}
667
668use crate::rings::local::AsLocalPIR;
669use crate::rings::multivariate::multivariate_impl::MultivariatePolyRingImpl;
670
671/// Computes a Groebner basis of the ideal generated by the input basis w.r.t. the given term
672/// ordering.
673///
674/// For a variant of this function that allows for more configuration, see [`buchberger()`].
675pub fn buchberger_simple<P, O>(ring: P, input_basis: Vec<El<P>>, order: O) -> Vec<El<P>>
676where
677    P: RingStore + Copy + Send + Sync,
678    El<P>: Send + Sync,
679    P::Type: MultivariatePolyRing,
680    <P::Type as RingExtension>::BaseRing: Sync,
681    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field,
682    O: MonomialOrder + Copy + Send + Sync,
683    PolyCoeff<P>: Send + Sync,
684{
685    let as_local_pir = AsLocalPIR::from_field(ring.base_ring());
686    let new_poly_ring = MultivariatePolyRingImpl::new(&as_local_pir, ring.indeterminate_count());
687    let from_ring = new_poly_ring.lifted_hom(ring, WrapHom::to_delegate_ring(as_local_pir.get_ring()));
688    let result = buchberger::<_, _, _, _, _>(
689        &new_poly_ring,
690        input_basis.into_iter().map(|f| from_ring.map(f)).collect(),
691        order,
692        default_sort_fn(&new_poly_ring, order),
693        |_| false,
694        DontObserve,
695    )
696    .unwrap_or_else(no_error);
697    let to_ring = ring.lifted_hom(&new_poly_ring, UnwrapHom::from_delegate_ring(as_local_pir.get_ring()));
698    return result.into_iter().map(|f| to_ring.map(f)).collect();
699}
700
701#[cfg(test)]
702use crate::integer::BigIntRing;
703#[cfg(test)]
704use crate::rings::poly::{PolyRingStore, dense_poly};
705#[cfg(test)]
706use crate::rings::rational::RationalField;
707#[cfg(test)]
708use crate::rings::zn::zn_static;
709
710#[test]
711fn test_buchberger_small() {
712    let base = zn_static::F17;
713    let ring = MultivariatePolyRingImpl::new(base, 2);
714
715    let f1 = ring.from_terms(
716        [
717            (1, ring.create_monomial([2, 0])),
718            (1, ring.create_monomial([0, 2])),
719            (16, ring.create_monomial([0, 0])),
720        ]
721        .into_iter(),
722    );
723    let f2 = ring.from_terms([(1, ring.create_monomial([1, 1])), (15, ring.create_monomial([0, 0]))].into_iter());
724
725    let actual = buchberger(
726        &ring,
727        vec![ring.clone_el(&f1), ring.clone_el(&f2)],
728        DegRevLex,
729        default_sort_fn(&ring, DegRevLex),
730        |_| false,
731        TEST_LOG_PROGRESS,
732    )
733    .unwrap_or_else(no_error);
734
735    let expected = ring.from_terms(
736        [
737            (16, ring.create_monomial([0, 3])),
738            (15, ring.create_monomial([1, 0])),
739            (1, ring.create_monomial([0, 1])),
740        ]
741        .into_iter(),
742    );
743
744    assert_eq!(3, actual.len());
745    assert_el_eq!(
746        ring,
747        ring.zero(),
748        multivariate_division(&ring, f1, actual.iter(), DegRevLex)
749    );
750    assert_el_eq!(
751        ring,
752        ring.zero(),
753        multivariate_division(&ring, f2, actual.iter(), DegRevLex)
754    );
755    assert_el_eq!(
756        ring,
757        ring.zero(),
758        multivariate_division(&ring, expected, actual.iter(), DegRevLex)
759    );
760}
761
762#[test]
763fn test_buchberger_larger() {
764    let base = zn_static::F17;
765    let ring = MultivariatePolyRingImpl::new(base, 3);
766
767    let f1 = ring.from_terms(
768        [
769            (1, ring.create_monomial([2, 1, 1])),
770            (1, ring.create_monomial([0, 2, 0])),
771            (1, ring.create_monomial([1, 0, 1])),
772            (2, ring.create_monomial([1, 0, 0])),
773            (1, ring.create_monomial([0, 0, 0])),
774        ]
775        .into_iter(),
776    );
777    let f2 = ring.from_terms(
778        [
779            (1, ring.create_monomial([0, 3, 1])),
780            (1, ring.create_monomial([0, 0, 3])),
781            (1, ring.create_monomial([1, 1, 0])),
782        ]
783        .into_iter(),
784    );
785    let f3 = ring.from_terms(
786        [
787            (1, ring.create_monomial([1, 0, 2])),
788            (1, ring.create_monomial([1, 0, 1])),
789            (2, ring.create_monomial([0, 1, 1])),
790            (7, ring.create_monomial([0, 0, 0])),
791        ]
792        .into_iter(),
793    );
794
795    let actual = buchberger(
796        &ring,
797        vec![ring.clone_el(&f1), ring.clone_el(&f2), ring.clone_el(&f3)],
798        DegRevLex,
799        default_sort_fn(&ring, DegRevLex),
800        |_| false,
801        TEST_LOG_PROGRESS,
802    )
803    .unwrap_or_else(no_error);
804
805    let g1 = ring.from_terms(
806        [
807            (1, ring.create_monomial([0, 4, 0])),
808            (8, ring.create_monomial([0, 3, 1])),
809            (12, ring.create_monomial([0, 1, 3])),
810            (6, ring.create_monomial([0, 0, 4])),
811            (1, ring.create_monomial([0, 3, 0])),
812            (13, ring.create_monomial([0, 2, 1])),
813            (11, ring.create_monomial([0, 1, 2])),
814            (10, ring.create_monomial([0, 0, 3])),
815            (11, ring.create_monomial([0, 2, 0])),
816            (12, ring.create_monomial([0, 1, 1])),
817            (6, ring.create_monomial([0, 0, 2])),
818            (6, ring.create_monomial([0, 1, 0])),
819            (13, ring.create_monomial([0, 0, 1])),
820            (9, ring.create_monomial([0, 0, 0])),
821        ]
822        .into_iter(),
823    );
824
825    assert_el_eq!(
826        ring,
827        ring.zero(),
828        multivariate_division(&ring, g1, actual.iter(), DegRevLex)
829    );
830}
831
832#[test]
833fn test_generic_computation() {
834    let base = zn_static::F17;
835    let ring = MultivariatePolyRingImpl::new(base, 6);
836    let poly_ring = dense_poly::DensePolyRing::new(&ring, "X");
837
838    let var_i = |i: usize| {
839        ring.create_term(
840            base.one(),
841            ring.create_monomial((0..ring.indeterminate_count()).map(|j| if i == j { 1 } else { 0 })),
842        )
843    };
844    let X1 = poly_ring.mul(
845        poly_ring.from_terms([(var_i(0), 0), (ring.one(), 1)].into_iter()),
846        poly_ring.from_terms([(var_i(1), 0), (ring.one(), 1)].into_iter()),
847    );
848    let X2 = poly_ring.mul(
849        poly_ring.add(
850            poly_ring.clone_el(&X1),
851            poly_ring.from_terms([(var_i(2), 0), (var_i(3), 1)].into_iter()),
852        ),
853        poly_ring.add(
854            poly_ring.clone_el(&X1),
855            poly_ring.from_terms([(var_i(4), 0), (var_i(5), 1)].into_iter()),
856        ),
857    );
858    let basis = vec![
859        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 0)),
860        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 1)),
861        ring.sub_ref_snd(ring.int_hom().map(1), poly_ring.coefficient_at(&X2, 2)),
862    ];
863
864    let start = std::time::Instant::now();
865    let gb1 = buchberger(
866        &ring,
867        basis.iter().map(|f| ring.clone_el(f)).collect(),
868        DegRevLex,
869        default_sort_fn(&ring, DegRevLex),
870        |_| false,
871        TEST_LOG_PROGRESS,
872    )
873    .unwrap_or_else(no_error);
874    let end = std::time::Instant::now();
875
876    println!("Computed GB in {} ms", (end - start).as_millis());
877
878    assert_eq!(11, gb1.len());
879}
880
881#[test]
882fn test_gb_local_ring() {
883    let base = AsLocalPIR::from_zn(zn_static::Zn::<16>::RING).unwrap();
884    let ring: MultivariatePolyRingImpl<_> = MultivariatePolyRingImpl::new(base, 1);
885
886    let f = ring.from_terms(
887        [
888            (base.int_hom().map(4), ring.create_monomial([1])),
889            (base.one(), ring.create_monomial([0])),
890        ]
891        .into_iter(),
892    );
893    let gb = buchberger(
894        &ring,
895        vec![f],
896        DegRevLex,
897        default_sort_fn(&ring, DegRevLex),
898        |_| false,
899        TEST_LOG_PROGRESS,
900    )
901    .unwrap_or_else(no_error);
902
903    assert_eq!(1, gb.len());
904    assert_el_eq!(ring, ring.one(), gb[0]);
905}
906
907#[test]
908fn test_gb_lex() {
909    let ZZ = BigIntRing::RING;
910    let QQ = AsLocalPIR::from_field(RationalField::new(ZZ));
911    let QQYX = MultivariatePolyRingImpl::new(&QQ, 2);
912    let [f, g] = QQYX.with_wrapped_indeterminates(|[Y, X]| {
913        [
914            1 + X.pow_ref(2) + 2 * Y + (1 + X) * Y.pow_ref(2),
915            3 + X + (2 + X) * Y + (1 + X + X.pow_ref(2)) * Y.pow_ref(2),
916        ]
917    });
918    let expected = QQYX.with_wrapped_indeterminates(|[Y, X]| {
919        [
920            X.pow_ref(8) + 2 * X.pow_ref(7) + 3 * X.pow_ref(6)
921                - 5 * X.pow_ref(5)
922                - 10 * X.pow_ref(4)
923                - 7 * X.pow_ref(3)
924                + 8 * X.pow_ref(2)
925                + 8 * X
926                + 4,
927            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,
928        ]
929    });
930
931    let mut gb = buchberger_simple(&QQYX, vec![f, g], Lex);
932
933    assert_eq!(2, gb.len());
934    gb.sort_unstable_by_key(|f| QQYX.appearing_indeterminates(f).len());
935    for (mut f, mut e) in gb.into_iter().zip(expected.into_iter()) {
936        let f_lc_inv = QQ.invert(QQYX.LT(&f, Lex).unwrap().0).unwrap();
937        QQYX.inclusion().mul_assign_map(&mut f, f_lc_inv);
938        let e_lc_inv = QQ.invert(QQYX.LT(&e, Lex).unwrap().0).unwrap();
939        QQYX.inclusion().mul_assign_map(&mut e, e_lc_inv);
940        assert_el_eq!(QQYX, e, f);
941    }
942}
943
944#[cfg(test)]
945#[cfg(feature = "parallel")]
946static TEST_COMPUTATION_CONTROLLER: ExecuteMultithreaded<LogProgress> = RunMultithreadedLogProgress;
947#[cfg(test)]
948#[cfg(not(feature = "parallel"))]
949static TEST_COMPUTATION_CONTROLLER: LogProgress = TEST_LOG_PROGRESS;
950
951#[ignore]
952#[test]
953fn test_expensive_gb_1() {
954    let base = AsLocalPIR::from_zn(zn_static::Zn::<16>::RING).unwrap();
955    let ring: MultivariatePolyRingImpl<_> = MultivariatePolyRingImpl::new(base, 12);
956
957    let system = ring.with_wrapped_indeterminates(|[Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8, Y9, Y10, Y11]| {
958        [
959            Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y4.pow_ref(2)
960                + 4 * Y0 * Y1 * Y2 * Y3 * Y4 * Y4 * Y8
961                + Y0 * Y1 * Y2 * Y5.pow_ref(2) * Y8.pow_ref(2)
962                + Y0 * Y2 * Y3 * Y4 * Y6
963                + Y0 * Y1 * Y3 * Y4 * Y7
964                + Y0 * Y2 * Y5 * Y6 * Y8
965                + Y0 * Y1 * Y5 * Y7 * Y8
966                + Y0 * Y2 * Y3 * Y5 * Y10
967                + Y0 * Y1 * Y3 * Y5 * Y11
968                + Y0 * Y6 * Y7
969                + Y3 * Y5 * Y9
970                - 4,
971            2 * Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y4 * Y5
972                + 2 * Y0 * Y1 * Y2 * Y3 * Y5.pow_ref(2) * Y8
973                + Y0 * Y2 * Y3 * Y5 * Y6
974                + Y0 * Y1 * Y3 * Y5 * Y7
975                + 8,
976            Y0 * Y1 * Y2 * Y3.pow_ref(2) * Y5.pow_ref(2) - 5,
977        ]
978    });
979
980    let part_of_result =
981        ring.with_wrapped_indeterminates(|[_Y0, Y1, Y2, _Y3, _Y4, _Y5, Y6, Y7, _Y8, _Y9, _Y10, _Y11]| {
982            [
983                4 * Y2.pow_ref(2) * Y6.pow_ref(2) - 4 * Y1.pow_ref(2) * Y7.pow_ref(2),
984                8 * Y2 * Y6 + 8 * Y1 * Y7.clone(),
985            ]
986        });
987
988    let start = std::time::Instant::now();
989    let gb = buchberger(
990        &ring,
991        system.iter().map(|f| ring.clone_el(f)).collect(),
992        DegRevLex,
993        default_sort_fn(&ring, DegRevLex),
994        |_| false,
995        TEST_COMPUTATION_CONTROLLER,
996    )
997    .unwrap_or_else(no_error);
998    let end = std::time::Instant::now();
999
1000    println!("Computed GB in {} ms", (end - start).as_millis());
1001
1002    for f in &part_of_result {
1003        assert!(ring.is_zero(&multivariate_division(&ring, ring.clone_el(f), gb.iter(), DegRevLex)));
1004    }
1005
1006    assert_eq!(108, gb.len());
1007}
1008
1009#[test]
1010#[ignore]
1011fn test_expensive_gb_2() {
1012    let base = zn_static::Fp::<7>::RING;
1013    let ring = MultivariatePolyRingImpl::new(base, 7);
1014
1015    let basis = ring.with_wrapped_indeterminates_dyn(|[X0, X1, X2, X3, X4, X5, X6]| {
1016        [
1017            6 + 2 * X5
1018                + 2 * X4
1019                + X6
1020                + 4 * X0
1021                + 5 * X6 * X5
1022                + X6 * X4
1023                + 3 * X0 * X4
1024                + 6 * X0 * X6
1025                + 2 * X0 * X3
1026                + X0 * X2
1027                + 4 * X0 * X1
1028                + 2 * X3 * X4 * X5
1029                + 4 * X0 * X6 * X5
1030                + 6 * X0 * X2 * X5
1031                + 5 * X0 * X6 * X4
1032                + 2 * X0 * X3 * X4
1033                + 4 * X0 * X1 * X4
1034                + X0 * X6.pow_ref(2)
1035                + 3 * X0 * X3 * X6
1036                + 5 * X0 * X2 * X6
1037                + 2 * X0 * X1 * X6
1038                + X0 * X3.pow_ref(2)
1039                + 2 * X0 * X2 * X3
1040                + 3 * X0 * X3 * X4 * X5
1041                + 4 * X0 * X3 * X6 * X5
1042                + 3 * X0 * X1 * X6 * X5
1043                + 3 * X0 * X2 * X3 * X5
1044                + 3 * X0 * X3 * X6 * X4
1045                + 2 * X0 * X1 * X6 * X4
1046                + 2 * X0 * X3.pow_ref(2) * X4
1047                + 2 * X0 * X2 * X3 * X4
1048                + 3 * X0 * X3.pow_ref(2) * X4 * X5
1049                + 4 * X0 * X1 * X3 * X4 * X5
1050                + X0 * X3.pow_ref(2) * X4.pow_ref(2),
1051            5 + 4 * X0
1052                + 6 * X4 * X5
1053                + 3 * X6 * X5
1054                + 4 * X0 * X4
1055                + 3 * X0 * X6
1056                + 6 * X0 * X3
1057                + 6 * X0 * X2
1058                + 6 * X6 * X4 * X5
1059                + 2 * X0 * X4 * X5
1060                + 4 * X0 * X6 * X5
1061                + 3 * X0 * X2 * X5
1062                + 3 * X0 * X6 * X4
1063                + 5 * X0 * X3 * X4
1064                + 6 * X0 * X2 * X4
1065                + 4 * X0 * X6.pow_ref(2)
1066                + 3 * X0 * X3 * X6
1067                + 3 * X0 * X2 * X6
1068                + 2 * X0 * X6 * X4 * X5
1069                + 6 * X0 * X3 * X4 * X5
1070                + 5 * X0 * X1 * X4 * X5
1071                + 6 * X0 * X6.pow_ref(2) * X5
1072                + 2 * X0 * X3 * X6 * X5
1073                + 2 * X0 * X2 * X6 * X5
1074                + 6 * X0 * X1 * X6 * X5
1075                + 6 * X0 * X2 * X3 * X5
1076                + 6 * X0 * X3 * X4.pow_ref(2)
1077                + 4 * X0 * X6.pow_ref(2) * X4
1078                + 6 * X0 * X3 * X6 * X4
1079                + 3 * X0 * X2 * X6 * X4
1080                + 4 * X0 * X3 * X6 * X4 * X5
1081                + 5 * X0 * X1 * X6 * X4 * X5
1082                + 6 * X0 * X3.pow_ref(2) * X4 * X5
1083                + 5 * X0 * X2 * X3 * X4 * X5
1084                + 3 * X0 * X3 * X6 * X4.pow_ref(2)
1085                + 6 * X0 * X3.pow_ref(2) * X4.pow_ref(2) * X5.clone(),
1086            2 + 2 * X0
1087                + 4 * X0 * X4
1088                + 2 * X0 * X6
1089                + 5 * X0 * X4 * X5
1090                + 2 * X0 * X6 * X5
1091                + 4 * X0 * X2 * X5
1092                + 2 * X0 * X4.pow_ref(2)
1093                + 4 * X0 * X6 * X4
1094                + 4 * X0 * X6.pow_ref(2)
1095                + 2 * X6 * X4 * X5.pow_ref(2)
1096                + 4 * X0 * X6 * X4 * X5
1097                + X0 * X3 * X4 * X5
1098                + X0 * X2 * X4 * X5
1099                + 3 * X0 * X6.pow_ref(2) * X5
1100                + 2 * X0 * X3 * X6 * X5
1101                + 4 * X0 * X2 * X6 * X5
1102                + 2 * X0 * X6 * X4.pow_ref(2)
1103                + X0 * X6.pow_ref(2) * X4
1104                + 3 * X0 * X6 * X4 * X5.pow_ref(2)
1105                + 2 * X0 * X6.pow_ref(2) * X5.pow_ref(2)
1106                + 3 * X0 * X2 * X6 * X5.pow_ref(2)
1107                + X0 * X3 * X4.pow_ref(2) * X5
1108                + X0 * X6.pow_ref(2) * X4 * X5
1109                + X0 * X3 * X6 * X4 * X5
1110                + 6 * X0 * X2 * X6 * X4 * X5
1111                + 4 * X0 * X6.pow_ref(2) * X4.pow_ref(2)
1112                + 6 * X0 * X3 * X6 * X4 * X5.pow_ref(2)
1113                + 4 * X0 * X1 * X6 * X4 * X5.pow_ref(2)
1114                + 4 * X0 * X2 * X3 * X4 * X5.pow_ref(2)
1115                + 6 * X0 * X3 * X6 * X4.pow_ref(2) * X5
1116                + 2 * X0 * X3.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(2),
1117            4 + 5 * X0 * X4 * X5
1118                + 6 * X0 * X6 * X5
1119                + 5 * X0 * X4.pow_ref(2) * X5
1120                + 3 * X0 * X6 * X4 * X5
1121                + 3 * X0 * X6.pow_ref(2) * X5
1122                + 6 * X0 * X6 * X4 * X5.pow_ref(2)
1123                + 5 * X0 * X2 * X4 * X5.pow_ref(2)
1124                + X0 * X6.pow_ref(2) * X5.pow_ref(2)
1125                + 6 * X0 * X2 * X6 * X5.pow_ref(2)
1126                + 4 * X0 * X6 * X4.pow_ref(2) * X5
1127                + 2 * X0 * X6.pow_ref(2) * X4 * X5
1128                + 5 * X0 * X3 * X4.pow_ref(2) * X5.pow_ref(2)
1129                + 3 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(2)
1130                + 5 * X0 * X3 * X6 * X4 * X5.pow_ref(2)
1131                + 4 * X0 * X2 * X6 * X4 * X5.pow_ref(2)
1132                + 6 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5
1133                + 4 * X0 * X3 * X6 * X4.pow_ref(2) * X5.pow_ref(2),
1134            4 + 4 * X0 * X4.pow_ref(2) * X5.pow_ref(2)
1135                + X0 * X6 * X4 * X5.pow_ref(2)
1136                + X0 * X6.pow_ref(2) * X5.pow_ref(2)
1137                + 5 * X0 * X6 * X4.pow_ref(2) * X5.pow_ref(2)
1138                + 6 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(2)
1139                + 3 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(3)
1140                + 4 * X0 * X2 * X6 * X4 * X5.pow_ref(3)
1141                + 6 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(2)
1142                + 4 * X0 * X3 * X6 * X4.pow_ref(2) * X5.pow_ref(3),
1143            5 * X0 * X6 * X4.pow_ref(2) * X5.pow_ref(3)
1144                + 6 * X0 * X6.pow_ref(2) * X4 * X5.pow_ref(3)
1145                + 5 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(3),
1146            2 * X0 * X6.pow_ref(2) * X4.pow_ref(2) * X5.pow_ref(4),
1147        ]
1148    });
1149
1150    let start = std::time::Instant::now();
1151    let gb = buchberger(
1152        &ring,
1153        basis,
1154        DegRevLex,
1155        default_sort_fn(&ring, DegRevLex),
1156        |_| false,
1157        TEST_COMPUTATION_CONTROLLER,
1158    )
1159    .unwrap_or_else(no_error);
1160    let end = std::time::Instant::now();
1161
1162    println!("Computed GB in {} ms", (end - start).as_millis());
1163
1164    assert_eq!(130, gb.len());
1165}
1166
1167#[test]
1168#[ignore]
1169fn test_groebner_cyclic6() {
1170    let base = zn_static::Fp::<65537>::RING;
1171    let ring = MultivariatePolyRingImpl::new(base, 6);
1172
1173    let cyclic6 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, t, u, v]| {
1174        [
1175            x + y + z + t + u + v,
1176            x * y + y * z + z * t + t * u + x * v + u * v,
1177            x * y * z + y * z * t + z * t * u + x * y * v + x * u * v + t * u * v,
1178            x * y * z * t + y * z * t * u + x * y * z * v + x * y * u * v + x * t * u * v + z * t * u * v,
1179            x * y * z * t * u
1180                + x * y * z * t * v
1181                + x * y * z * u * v
1182                + x * y * t * u * v
1183                + x * z * t * u * v
1184                + y * z * t * u * v,
1185            x * y * z * t * u * v - 1,
1186        ]
1187    });
1188
1189    let start = std::time::Instant::now();
1190    let gb = buchberger(
1191        &ring,
1192        cyclic6,
1193        DegRevLex,
1194        default_sort_fn(&ring, DegRevLex),
1195        |_| false,
1196        TEST_COMPUTATION_CONTROLLER,
1197    )
1198    .unwrap_or_else(no_error);
1199    let end = std::time::Instant::now();
1200
1201    println!("Computed GB in {} ms", (end - start).as_millis());
1202    assert_eq!(45, gb.len());
1203}
1204
1205#[test]
1206#[ignore]
1207fn test_groebner_cyclic7() {
1208    let base = zn_static::Fp::<65537>::RING;
1209    let ring = MultivariatePolyRingImpl::new(base, 7);
1210
1211    let cyclic7 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, t, u, v, w]| {
1212        [
1213            x + y + z + t + u + v + w,
1214            x * y + y * z + z * t + t * u + u * v + x * w + v * w,
1215            x * y * z + y * z * t + z * t * u + t * u * v + x * y * w + x * v * w + u * v * w,
1216            x * y * z * t
1217                + y * z * t * u
1218                + z * t * u * v
1219                + x * y * z * w
1220                + x * y * v * w
1221                + x * u * v * w
1222                + t * u * v * w,
1223            x * y * z * t * u
1224                + y * z * t * u * v
1225                + x * y * z * t * w
1226                + x * y * z * v * w
1227                + x * y * u * v * w
1228                + x * t * u * v * w
1229                + z * t * u * v * w,
1230            x * y * z * t * u * v
1231                + x * y * z * t * u * w
1232                + x * y * z * t * v * w
1233                + x * y * z * u * v * w
1234                + x * y * t * u * v * w
1235                + x * z * t * u * v * w
1236                + y * z * t * u * v * w,
1237            x * y * z * t * u * v * w - 1,
1238        ]
1239    });
1240
1241    let start = std::time::Instant::now();
1242    let gb = buchberger(
1243        &ring,
1244        cyclic7,
1245        DegRevLex,
1246        default_sort_fn(&ring, DegRevLex),
1247        |_| false,
1248        TEST_COMPUTATION_CONTROLLER,
1249    )
1250    .unwrap_or_else(no_error);
1251    let end = std::time::Instant::now();
1252
1253    println!("Computed GB in {} ms", (end - start).as_millis());
1254    assert_eq!(209, gb.len());
1255}
1256
1257#[test]
1258#[ignore]
1259fn test_groebner_cyclic8() {
1260    let base = zn_static::Fp::<65537>::RING;
1261    let ring = MultivariatePolyRingImpl::new(base, 8);
1262
1263    let cyclic7 = ring.with_wrapped_indeterminates_dyn(|[x, y, z, s, t, u, v, w]| {
1264        [
1265            x + y + z + s + t + u + v + w,
1266            x * y + y * z + z * s + s * t + t * u + u * v + x * w + v * w,
1267            x * y * z + y * z * s + z * s * t + s * t * u + t * u * v + x * y * w + x * v * w + u * v * w,
1268            x * y * z * s
1269                + y * z * s * t
1270                + z * s * t * u
1271                + s * t * u * v
1272                + x * y * z * w
1273                + x * y * v * w
1274                + x * u * v * w
1275                + t * u * v * w,
1276            x * y * z * s * t
1277                + y * z * s * t * u
1278                + z * s * t * u * v
1279                + x * y * z * s * w
1280                + x * y * z * v * w
1281                + x * y * u * v * w
1282                + x * t * u * v * w
1283                + s * t * u * v * w,
1284            x * y * z * s * t * u
1285                + y * z * s * t * u * v
1286                + x * y * z * s * t * w
1287                + x * y * z * s * v * w
1288                + x * y * z * u * v * w
1289                + x * y * t * u * v * w
1290                + x * s * t * u * v * w
1291                + z * s * t * u * v * w,
1292            x * y * z * s * t * u * v
1293                + x * y * z * s * t * u * w
1294                + x * y * z * s * t * v * w
1295                + x * y * z * s * u * v * w
1296                + x * y * z * t * u * v * w
1297                + x * y * s * t * u * v * w
1298                + x * z * s * t * u * v * w
1299                + y * z * s * t * u * v * w,
1300            x * y * z * s * t * u * v * w - 1,
1301        ]
1302    });
1303
1304    let start = std::time::Instant::now();
1305    let gb = buchberger(
1306        &ring,
1307        cyclic7,
1308        DegRevLex,
1309        default_sort_fn(&ring, DegRevLex),
1310        |_| false,
1311        TEST_COMPUTATION_CONTROLLER,
1312    )
1313    .unwrap_or_else(no_error);
1314    let end = std::time::Instant::now();
1315
1316    println!("Computed GB in {} ms", (end - start).as_millis());
1317    assert_eq!(372, gb.len());
1318}