noether/homalg/module_groebner/
integer.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Benjamin Antieau and Deven Manam
3//! Public strong module Gröbner values over integral polynomial rings.
4
5use core::fmt;
6use std::collections::VecDeque;
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use crate::commalg::{
11    from_integer_kernel_polynomial, FreeAbelianGrading, Polynomial, PolynomialRing,
12};
13use crate::{
14    Integer, IntegerRing, NoetherError, PrincipalIdealDomain, Ring, SemanticType, TypeDescription,
15};
16
17use super::{
18    compare_module_monomials, element_from_coordinates, exponent_to_kernel, is_zero, map_columns,
19    map_kernel_module_error, module_monomial_divides, monomial_lcm, monomial_quotient,
20    resource_limit, to_kernel_module_order, validate_schreyer_frame, ModuleElement, ModuleError,
21    ModuleGroebnerError, ModuleHomomorphism, ModuleMonomial, ModuleMonomialOrder, Submodule,
22    MAX_REDUCTION_STEPS,
23};
24use crate::homalg::FreeModule;
25
26static STRONG_MODULE_TERM_DESCRIPTION: TypeDescription = TypeDescription {
27    semantic_id: "noether/homalg/StrongModuleTerm@1",
28    rust_path: "noether::homalg::StrongModuleTerm",
29    python_path: Some("noether.homalg.StrongModuleTerm"),
30};
31
32static STRONG_MODULE_GROEBNER_BASIS_DESCRIPTION: TypeDescription = TypeDescription {
33    semantic_id: "noether/homalg/StrongModuleGroebnerBasis@1",
34    rust_path: "noether::homalg::StrongModuleGroebnerBasis",
35    python_path: Some("noether.homalg.StrongModuleGroebnerBasis"),
36};
37
38static STRONG_MODULE_REDUCTION_DESCRIPTION: TypeDescription = TypeDescription {
39    semantic_id: "noether/homalg/StrongModuleReduction@1",
40    rust_path: "noether::homalg::StrongModuleReduction",
41    python_path: Some("noether.homalg.StrongModuleReduction"),
42};
43
44/// A positive integer coefficient times one leading module monomial.
45///
46/// Strong bases over `Z` retain this coefficient because divisibility of
47/// leading coefficients is part of the initial module and of reduction.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct StrongModuleTerm {
50    monomial: ModuleMonomial,
51    coefficient: Integer,
52}
53
54#[derive(Serialize, Deserialize)]
55#[serde(deny_unknown_fields)]
56struct StrongModuleTermWire {
57    #[serde(rename = "type")]
58    type_id: String,
59    monomial: ModuleMonomial,
60    coefficient: Integer,
61}
62
63impl StrongModuleTerm {
64    fn new(monomial: ModuleMonomial, coefficient: Integer) -> Self {
65        Self {
66            monomial,
67            coefficient,
68        }
69    }
70
71    /// Leading polynomial monomial and free-module component.
72    pub fn monomial(&self) -> &ModuleMonomial {
73        &self.monomial
74    }
75
76    /// Positive leading coefficient.
77    pub fn coefficient(&self) -> &Integer {
78        &self.coefficient
79    }
80
81    /// Serialize this exact strong leading term.
82    pub fn to_json(&self) -> Result<String, NoetherError> {
83        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
84    }
85
86    /// Deserialize and validate a strong leading term.
87    pub fn from_json(value: &str) -> Result<Self, NoetherError> {
88        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
89    }
90}
91
92impl Serialize for StrongModuleTerm {
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        StrongModuleTermWire {
98            type_id: Self::TYPE_ID.to_owned(),
99            monomial: self.monomial.clone(),
100            coefficient: self.coefficient.clone(),
101        }
102        .serialize(serializer)
103    }
104}
105
106impl<'de> Deserialize<'de> for StrongModuleTerm {
107    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108    where
109        D: Deserializer<'de>,
110    {
111        let wire = StrongModuleTermWire::deserialize(deserializer)?;
112        if wire.type_id != Self::TYPE_ID {
113            return Err(serde::de::Error::custom(format!(
114                "expected type {}, received {}",
115                Self::TYPE_ID,
116                wire.type_id
117            )));
118        }
119        if !wire.coefficient.is_positive() {
120            return Err(serde::de::Error::custom(
121                "a strong module leading coefficient must be positive",
122            ));
123        }
124        Ok(Self::new(wire.monomial, wire.coefficient))
125    }
126}
127
128impl SemanticType for StrongModuleTerm {
129    const TYPE_ID: &'static str = "noether/homalg/StrongModuleTerm@1";
130
131    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
132        Ok(self.to_json()?.into_bytes())
133    }
134
135    fn describe() -> &'static TypeDescription {
136        &STRONG_MODULE_TERM_DESCRIPTION
137    }
138}
139
140/// A verified normalized strong Gröbner basis for a submodule of
141/// `Z[x_1,...,x_n]^r`.
142///
143/// Unlike a field basis, its generators need not be monic. The retained
144/// transformation map proves that the basis lies in the original submodule;
145/// strong S/G-pair verification and reduction of every input generator prove
146/// the reverse containment and completeness.
147#[derive(Clone)]
148pub struct StrongModuleGroebnerBasis {
149    input: Submodule<PolynomialRing<IntegerRing>>,
150    order: ModuleMonomialOrder,
151    basis: Submodule<PolynomialRing<IntegerRing>>,
152    transformation: ModuleHomomorphism<PolynomialRing<IntegerRing>>,
153    leading_terms: Vec<StrongModuleTerm>,
154}
155
156/// Exact Euclidean reduction and lift evidence against an integral strong
157/// module basis.
158#[derive(Clone)]
159pub struct StrongModuleReduction {
160    basis: StrongModuleGroebnerBasis,
161    value: ModuleElement<PolynomialRing<IntegerRing>>,
162    quotient: ModuleElement<PolynomialRing<IntegerRing>>,
163    original_combination: ModuleElement<PolynomialRing<IntegerRing>>,
164    remainder: ModuleElement<PolynomialRing<IntegerRing>>,
165}
166
167impl StrongModuleGroebnerBasis {
168    /// Original generated submodule.
169    pub fn input(&self) -> &Submodule<PolynomialRing<IntegerRing>> {
170        &self.input
171    }
172
173    /// Exact module-monomial order.
174    pub fn order(&self) -> &ModuleMonomialOrder {
175        &self.order
176    }
177
178    /// Canonically ordered positive-leading-coefficient strong basis.
179    pub fn basis(&self) -> &Submodule<PolynomialRing<IntegerRing>> {
180        &self.basis
181    }
182
183    /// Map expressing every strong-basis generator in the original family.
184    pub fn transformation(&self) -> &ModuleHomomorphism<PolynomialRing<IntegerRing>> {
185        &self.transformation
186    }
187
188    /// Ordered coefficient-bearing leading terms.
189    pub fn leading_terms(&self) -> &[StrongModuleTerm] {
190        &self.leading_terms
191    }
192
193    /// Initial submodule generated by the full coefficient-bearing leading
194    /// terms.
195    pub fn initial_submodule(
196        &self,
197    ) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
198        let ring = self.input.ambient().ring();
199        let generators = self
200            .leading_terms
201            .iter()
202            .map(|leading| {
203                let mut coordinates = vec![ring.zero(); self.input.ambient().rank()];
204                coordinates[leading.monomial.component()] = ring.from_terms([(
205                    leading.monomial.exponent().to_vec(),
206                    leading.coefficient.clone(),
207                )])?;
208                element_from_coordinates(self.input.ambient().clone(), coordinates)
209                    .map_err(ModuleGroebnerError::from)
210            })
211            .collect::<Result<Vec<_>, ModuleGroebnerError>>()?;
212        integral_submodule_from_generators(self.input.ambient(), generators)
213    }
214
215    /// This value is complete; bounded failures do not return partial bases.
216    pub const fn is_complete(&self) -> bool {
217        true
218    }
219
220    /// Reduce an ambient value, retaining canonical-basis quotients and an
221    /// exact lift into the original generators.
222    pub fn reduce(
223        &self,
224        value: &ModuleElement<PolynomialRing<IntegerRing>>,
225    ) -> Result<StrongModuleReduction, ModuleGroebnerError> {
226        if value.module() != self.input.ambient() {
227            return Err(ModuleGroebnerError::InvalidEvidence(
228                "the reduced value belongs to another ambient module",
229            ));
230        }
231        let ring = self.input.ambient().ring();
232        let basis_values = map_columns(self.basis.generator_map())?;
233        let (remainder, quotients) = match (
234            to_kernel_module_order(&self.order),
235            to_integer_kernel_module_elements(&basis_values, ring, self.input.ambient().rank())?,
236            to_integer_kernel_module_elements(
237                std::slice::from_ref(value),
238                ring,
239                self.input.ambient().rank(),
240            )?,
241        ) {
242            (Some(kernel_order), Some(basis_kernel), Some(mut value_kernel)) => {
243                let value_kernel = value_kernel
244                    .pop()
245                    .expect("one input projects to one kernel value");
246                let output = groebner_kernel::strong_module_normal_form_with_quotients(
247                    &value_kernel,
248                    &basis_kernel,
249                    &kernel_order,
250                    MAX_REDUCTION_STEPS,
251                )
252                .map_err(map_kernel_module_error)?;
253                let remainder =
254                    from_integer_kernel_module_element(self.input.ambient(), output.remainder)?;
255                let quotients = output
256                    .quotients
257                    .into_iter()
258                    .map(|polynomial| {
259                        from_integer_kernel_polynomial(ring, polynomial)
260                            .map_err(super::map_polynomial_groebner_error)
261                    })
262                    .collect::<Result<Vec<_>, _>>()?;
263                (remainder, quotients)
264            }
265            _ => strong_normal_form_arbitrary(
266                value,
267                &basis_values,
268                &self.order,
269                MAX_REDUCTION_STEPS,
270            )?,
271        };
272        let quotient =
273            element_from_coordinates(self.basis.generator_map().source().clone(), quotients)?;
274        let original_combination = self.transformation.apply(&quotient)?;
275        let result = StrongModuleReduction {
276            basis: self.clone(),
277            value: value.clone(),
278            quotient,
279            original_combination,
280            remainder,
281        };
282        result.verify()?;
283        Ok(result)
284    }
285
286    /// Canonical Euclidean strong normal form.
287    pub fn normal_form(
288        &self,
289        value: &ModuleElement<PolynomialRing<IntegerRing>>,
290    ) -> Result<ModuleElement<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
291        Ok(self.reduce(value)?.remainder)
292    }
293
294    /// Whether an ambient value belongs to the original submodule.
295    pub fn contains(
296        &self,
297        value: &ModuleElement<PolynomialRing<IntegerRing>>,
298    ) -> Result<bool, ModuleGroebnerError> {
299        Ok(self.reduce(value)?.is_member())
300    }
301
302    /// Original-generator coordinates for a member.
303    pub fn lift(
304        &self,
305        value: &ModuleElement<PolynomialRing<IntegerRing>>,
306    ) -> Result<Option<ModuleElement<PolynomialRing<IntegerRing>>>, ModuleGroebnerError> {
307        let reduction = self.reduce(value)?;
308        Ok(reduction
309            .is_member()
310            .then(|| reduction.original_combination.clone()))
311    }
312
313    /// Complete relation module among the original integral generators.
314    pub fn syzygies(&self) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
315        strong_relations_submodule(&self.input)
316    }
317
318    /// Independently recompute and compare the canonical basis,
319    /// transformation, and coefficient-bearing leaders.
320    pub fn verify(&self) -> Result<(), ModuleGroebnerError> {
321        validate_integral_order(self.input.ambient(), &self.order)?;
322        if self.basis.ambient() != self.input.ambient()
323            || self.transformation.source() != self.basis.generator_map().source()
324            || self.transformation.target() != self.input.generator_map().source()
325            || self.input.generator_map().compose(&self.transformation)?
326                != *self.basis.generator_map()
327        {
328            return Err(ModuleGroebnerError::InvalidEvidence(
329                "the integral strong basis has incompatible or invalid change data",
330            ));
331        }
332        let expected = compute_strong_module_basis(&self.input, self.order.clone())?;
333        if expected.basis.generator_map() != self.basis.generator_map()
334            || expected.transformation != self.transformation
335            || expected.leading_terms != self.leading_terms
336        {
337            return Err(ModuleGroebnerError::InvalidEvidence(
338                "recomputation changed the normalized integral strong module basis",
339            ));
340        }
341        Ok(())
342    }
343
344    /// Serialize the complete integral strong-basis evidence.
345    pub fn to_json(&self) -> Result<String, NoetherError> {
346        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
347    }
348
349    /// Deserialize and independently recompute the strong basis.
350    pub fn from_json(value: &str) -> Result<Self, NoetherError> {
351        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
352    }
353}
354
355impl StrongModuleReduction {
356    /// Strong basis used for reduction.
357    pub fn basis(&self) -> &StrongModuleGroebnerBasis {
358        &self.basis
359    }
360
361    /// Ambient value that was reduced.
362    pub fn value(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
363        &self.value
364    }
365
366    /// Coordinates in the canonical strong-basis generators.
367    pub fn quotient(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
368        &self.quotient
369    }
370
371    /// Corresponding combination of the original input generators.
372    pub fn original_combination(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
373        &self.original_combination
374    }
375
376    /// Canonical Euclidean remainder.
377    pub fn remainder(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
378        &self.remainder
379    }
380
381    /// Whether this reduction proves membership.
382    pub fn is_member(&self) -> bool {
383        is_zero(&self.remainder)
384    }
385
386    /// Original-generator lift when the remainder vanishes.
387    pub fn lift(&self) -> Option<&ModuleElement<PolynomialRing<IntegerRing>>> {
388        self.is_member().then_some(&self.original_combination)
389    }
390
391    /// Verify both reconstruction identities.
392    pub fn verify(&self) -> Result<(), ModuleGroebnerError> {
393        if self.value.module() != self.basis.input.ambient()
394            || self.remainder.module() != self.basis.input.ambient()
395            || self.quotient.module() != self.basis.basis.generator_map().source()
396            || self.original_combination.module() != self.basis.input.generator_map().source()
397        {
398            return Err(ModuleGroebnerError::InvalidEvidence(
399                "integral module reduction components have incompatible modules",
400            ));
401        }
402        let basis_part = self.basis.basis.generator_map().apply(&self.quotient)?;
403        if basis_part.add(&self.remainder)? != self.value {
404            return Err(ModuleGroebnerError::InvalidEvidence(
405                "integral quotient and remainder do not reconstruct the value",
406            ));
407        }
408        if self.basis.transformation.apply(&self.quotient)? != self.original_combination {
409            return Err(ModuleGroebnerError::InvalidEvidence(
410                "the integral original-generator combination does not match the change map",
411            ));
412        }
413        let original_part = self
414            .basis
415            .input
416            .generator_map()
417            .apply(&self.original_combination)?;
418        if original_part.add(&self.remainder)? != self.value {
419            return Err(ModuleGroebnerError::InvalidEvidence(
420                "the integral lift and remainder do not reconstruct the value",
421            ));
422        }
423        Ok(())
424    }
425
426    /// Serialize exact integral reduction evidence.
427    pub fn to_json(&self) -> Result<String, NoetherError> {
428        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
429    }
430
431    /// Deserialize and verify exact integral reduction evidence.
432    pub fn from_json(value: &str) -> Result<Self, NoetherError> {
433        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
434    }
435}
436
437impl Submodule<PolynomialRing<IntegerRing>> {
438    /// Compute the complete normalized strong module Gröbner basis over `Z`.
439    pub fn strong_groebner_basis(
440        &self,
441        order: ModuleMonomialOrder,
442    ) -> Result<StrongModuleGroebnerBasis, ModuleGroebnerError> {
443        compute_strong_module_basis(self, order)
444    }
445}
446
447impl ModuleHomomorphism<PolynomialRing<IntegerRing>> {
448    /// Exact kernel as a generated integral polynomial submodule.
449    pub fn kernel(
450        &self,
451        order: ModuleMonomialOrder,
452    ) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
453        validate_integral_order(self.target(), &order)?;
454        if self.source().rank() == 0 {
455            return Submodule::from_generators(self.source().clone(), Vec::new())
456                .map_err(Into::into);
457        }
458        strong_relations_submodule(&Submodule::from_generator_map(
459            self.target().clone(),
460            self.clone(),
461        )?)
462    }
463}
464
465impl ModuleElement<PolynomialRing<IntegerRing>> {
466    /// Leading coefficient-bearing module term in the supplied order.
467    pub fn leading_module_term(
468        &self,
469        order: &ModuleMonomialOrder,
470    ) -> Result<Option<StrongModuleTerm>, ModuleGroebnerError> {
471        validate_integral_order(self.module(), order)?;
472        Ok(integral_leading_term(self, order)
473            .map(|(monomial, coefficient)| StrongModuleTerm::new(monomial, coefficient)))
474    }
475}
476
477fn compute_strong_module_basis(
478    input: &Submodule<PolynomialRing<IntegerRing>>,
479    order: ModuleMonomialOrder,
480) -> Result<StrongModuleGroebnerBasis, ModuleGroebnerError> {
481    validate_integral_order(input.ambient(), &order)?;
482    let values = map_columns(input.generator_map())?;
483    if values.is_empty() {
484        return project_strong_module_basis(input, order, Vec::new(), Vec::new());
485    }
486    let ring = input.ambient().ring();
487    if let (Some(kernel_order), Some(kernel_values)) = (
488        to_kernel_module_order(&order),
489        to_integer_kernel_module_elements(&values, ring, input.ambient().rank())?,
490    ) {
491        let output = groebner_kernel::strong_module_groebner_basis(
492            &kernel_values,
493            &kernel_order,
494            &groebner_kernel::StrongGroebnerOptions::default(),
495        )
496        .map_err(map_kernel_module_error)?;
497        return project_strong_module_basis(input, order, output.basis, output.transformations);
498    }
499    let tracked = strong_module_basis_arbitrary(
500        input,
501        &order,
502        &groebner_kernel::StrongGroebnerOptions::default(),
503    )?;
504    project_arbitrary_strong_module_basis(input, order, tracked)
505}
506
507type IntegralPolynomial = Polynomial<IntegerRing>;
508type IntegralModuleElement = ModuleElement<PolynomialRing<IntegerRing>>;
509
510#[derive(Clone, PartialEq, Eq)]
511struct ArbitraryTrackedIntegerModuleElement {
512    value: IntegralModuleElement,
513    representation: IntegralModuleElement,
514}
515
516fn strong_module_basis_arbitrary(
517    input: &Submodule<PolynomialRing<IntegerRing>>,
518    order: &ModuleMonomialOrder,
519    options: &groebner_kernel::StrongGroebnerOptions,
520) -> Result<Vec<ArbitraryTrackedIntegerModuleElement>, ModuleGroebnerError> {
521    validate_integral_order(input.ambient(), order)?;
522    let source = input.generator_map().source();
523    let inputs = map_columns(input.generator_map())?;
524    let mut basis = Vec::new();
525    for (index, value) in inputs.iter().cloned().enumerate() {
526        if is_zero(&value) {
527            continue;
528        }
529        let entry = normalize_arbitrary_tracked_sign(
530            ArbitraryTrackedIntegerModuleElement {
531                value,
532                representation: source.basis_element(index)?,
533            },
534            order,
535        )?;
536        if !basis
537            .iter()
538            .any(|known: &ArbitraryTrackedIntegerModuleElement| known.value == entry.value)
539        {
540            basis.push(entry);
541        }
542    }
543    if basis.len() > options.max_basis_size {
544        return Err(resource_limit(
545            "strong arbitrary-exponent module basis size",
546            options.max_basis_size,
547        ));
548    }
549
550    let mut pending = VecDeque::new();
551    for right in 1..basis.len() {
552        install_arbitrary_pairs(
553            &basis,
554            right,
555            &mut pending,
556            order,
557            options.max_pending_pairs,
558        )?;
559    }
560    let mut processed = 0_usize;
561    while let Some((left, right)) = pending.pop_front() {
562        if processed == options.max_processed_pairs {
563            return Err(resource_limit(
564                "strong arbitrary-exponent module processed pairs",
565                options.max_processed_pairs,
566            ));
567        }
568        processed += 1;
569        for candidate in arbitrary_integer_module_pairs(&basis[left], &basis[right], order)? {
570            let remainder = tracked_normal_form_arbitrary(
571                candidate,
572                &basis,
573                order,
574                options.max_reduction_steps,
575            )?;
576            if is_zero(&remainder.value) {
577                continue;
578            }
579            let remainder = normalize_arbitrary_tracked_sign(remainder, order)?;
580            if basis.iter().any(|known| known.value == remainder.value) {
581                continue;
582            }
583            if basis.len() == options.max_basis_size {
584                return Err(resource_limit(
585                    "strong arbitrary-exponent module basis size",
586                    options.max_basis_size,
587                ));
588            }
589            let next = basis.len();
590            basis.push(remainder);
591            install_arbitrary_pairs(&basis, next, &mut pending, order, options.max_pending_pairs)?;
592        }
593    }
594
595    autoreduce_arbitrary_tracked(&mut basis, order, options.max_reduction_steps)?;
596    verify_arbitrary_strong_module_basis(
597        input,
598        &inputs,
599        &basis,
600        order,
601        options.max_reduction_steps,
602    )?;
603    Ok(basis)
604}
605
606fn project_arbitrary_strong_module_basis(
607    input: &Submodule<PolynomialRing<IntegerRing>>,
608    order: ModuleMonomialOrder,
609    tracked: Vec<ArbitraryTrackedIntegerModuleElement>,
610) -> Result<StrongModuleGroebnerBasis, ModuleGroebnerError> {
611    let leading_terms = tracked
612        .iter()
613        .map(|entry| {
614            integral_leading_term(&entry.value, &order)
615                .map(|(monomial, coefficient)| StrongModuleTerm::new(monomial, coefficient))
616                .ok_or(ModuleGroebnerError::InvalidEvidence(
617                    "the arbitrary-exponent strong basis contains zero",
618                ))
619        })
620        .collect::<Result<Vec<_>, _>>()?;
621    let (basis_values, representation_values): (Vec<_>, Vec<_>) = tracked
622        .into_iter()
623        .map(|entry| (entry.value, entry.representation))
624        .unzip();
625    let basis = integral_submodule_from_generators(input.ambient(), basis_values)?;
626    let transformation = ModuleHomomorphism::from_coordinate_columns(
627        basis.generator_map().source().clone(),
628        input.generator_map().source().clone(),
629        representation_values
630            .into_iter()
631            .map(|value| value.coordinates().values().collect())
632            .collect(),
633    )?;
634    Ok(StrongModuleGroebnerBasis {
635        input: input.clone(),
636        order,
637        basis,
638        transformation,
639        leading_terms,
640    })
641}
642
643fn install_arbitrary_pairs(
644    basis: &[ArbitraryTrackedIntegerModuleElement],
645    new_index: usize,
646    pending: &mut VecDeque<(usize, usize)>,
647    order: &ModuleMonomialOrder,
648    max_pending_pairs: usize,
649) -> Result<(), ModuleGroebnerError> {
650    let new_component = integral_leading_term(&basis[new_index].value, order)
651        .expect("an installed arbitrary-exponent strong basis member is nonzero")
652        .0
653        .component();
654    for (old, entry) in basis.iter().enumerate().take(new_index) {
655        if integral_leading_term(&entry.value, order)
656            .expect("an installed arbitrary-exponent strong basis member is nonzero")
657            .0
658            .component()
659            != new_component
660        {
661            continue;
662        }
663        if pending.len() == max_pending_pairs {
664            return Err(resource_limit(
665                "strong arbitrary-exponent module pending pairs",
666                max_pending_pairs,
667            ));
668        }
669        pending.push_back((old, new_index));
670    }
671    Ok(())
672}
673
674fn arbitrary_integer_module_pairs(
675    left: &ArbitraryTrackedIntegerModuleElement,
676    right: &ArbitraryTrackedIntegerModuleElement,
677    order: &ModuleMonomialOrder,
678) -> Result<[ArbitraryTrackedIntegerModuleElement; 2], ModuleGroebnerError> {
679    let (left_term, left_coefficient) = integral_leading_term(&left.value, order).ok_or(
680        ModuleGroebnerError::InvalidEvidence("an arbitrary-exponent strong pair contains zero"),
681    )?;
682    let (right_term, right_coefficient) = integral_leading_term(&right.value, order).ok_or(
683        ModuleGroebnerError::InvalidEvidence("an arbitrary-exponent strong pair contains zero"),
684    )?;
685    if left_term.component() != right_term.component() {
686        return Err(ModuleGroebnerError::InvalidEvidence(
687            "a strong module pair has unequal leading components",
688        ));
689    }
690    let ring = left.value.module().ring();
691    let lcm = monomial_lcm(left_term.exponent(), right_term.exponent());
692    let coefficient_lcm = left_coefficient.abs().lcm(&right_coefficient.abs());
693    let left_exponent = monomial_quotient(&lcm, left_term.exponent());
694    let right_exponent = monomial_quotient(&lcm, right_term.exponent());
695    let s_left =
696        ring.from_terms([(left_exponent.clone(), &coefficient_lcm / &left_coefficient)])?;
697    let s_right = ring.from_terms([(
698        right_exponent.clone(),
699        &coefficient_lcm / &right_coefficient,
700    )])?;
701    let s = combine_arbitrary_tracked(left, &s_left, right, &s_right, false)?;
702
703    let bezout = IntegerRing.extended_gcd(&left_coefficient, &right_coefficient);
704    let g_left = ring.from_terms([(left_exponent, bezout.coefficient_a)])?;
705    let g_right = ring.from_terms([(right_exponent, bezout.coefficient_b)])?;
706    let g = combine_arbitrary_tracked(left, &g_left, right, &g_right, true)?;
707    Ok([s, g])
708}
709
710fn combine_arbitrary_tracked(
711    left: &ArbitraryTrackedIntegerModuleElement,
712    left_factor: &IntegralPolynomial,
713    right: &ArbitraryTrackedIntegerModuleElement,
714    right_factor: &IntegralPolynomial,
715    add_right: bool,
716) -> Result<ArbitraryTrackedIntegerModuleElement, ModuleGroebnerError> {
717    let left_value = left.value.scalar_mul(left_factor)?;
718    let right_value = right.value.scalar_mul(right_factor)?;
719    let left_representation = left.representation.scalar_mul(left_factor)?;
720    let right_representation = right.representation.scalar_mul(right_factor)?;
721    Ok(ArbitraryTrackedIntegerModuleElement {
722        value: if add_right {
723            left_value.add(&right_value)?
724        } else {
725            left_value.sub(&right_value)?
726        },
727        representation: if add_right {
728            left_representation.add(&right_representation)?
729        } else {
730            left_representation.sub(&right_representation)?
731        },
732    })
733}
734
735fn strong_normal_form_arbitrary(
736    value: &IntegralModuleElement,
737    basis: &[IntegralModuleElement],
738    order: &ModuleMonomialOrder,
739    max_steps: usize,
740) -> Result<(IntegralModuleElement, Vec<IntegralPolynomial>), ModuleGroebnerError> {
741    validate_integral_order(value.module(), order)?;
742    if basis.iter().any(|entry| entry.module() != value.module()) {
743        return Err(ModuleGroebnerError::InvalidEvidence(
744            "arbitrary-exponent strong reducers belong to another module",
745        ));
746    }
747    require_arbitrary_positive_leaders(basis, order)?;
748    let ring = value.module().ring();
749    let mut work = value.clone();
750    let mut remainder = ModuleElement::zero(value.module().clone());
751    let mut quotients = vec![ring.zero(); basis.len()];
752    let mut steps = 0_usize;
753    while let Some((work_term, work_coefficient)) = integral_leading_term(&work, order) {
754        if steps == max_steps {
755            return Err(resource_limit(
756                "strong arbitrary-exponent module normal-form steps",
757                max_steps,
758            ));
759        }
760        let reduction = basis.iter().enumerate().find_map(|(index, reducer)| {
761            let (reducer_term, reducer_coefficient) = integral_leading_term(reducer, order)?;
762            if !module_monomial_divides(&reducer_term, &work_term) {
763                return None;
764            }
765            let quotient = work_coefficient.div_floor(&reducer_coefficient.abs());
766            (!quotient.is_zero()).then_some((index, reducer, reducer_term, quotient))
767        });
768        if let Some((index, reducer, reducer_term, quotient)) = reduction {
769            let factor = ring.from_terms([(
770                monomial_quotient(work_term.exponent(), reducer_term.exponent()),
771                quotient,
772            )])?;
773            quotients[index] = ring.add(&quotients[index], &factor)?;
774            work = work.sub(&reducer.scalar_mul(&factor)?)?;
775        } else {
776            let term = integral_module_term(value.module(), work_term, work_coefficient)?;
777            remainder = remainder.add(&term)?;
778            work = work.sub(&term)?;
779        }
780        steps += 1;
781    }
782    Ok((remainder, quotients))
783}
784
785fn tracked_normal_form_arbitrary(
786    mut current: ArbitraryTrackedIntegerModuleElement,
787    basis: &[ArbitraryTrackedIntegerModuleElement],
788    order: &ModuleMonomialOrder,
789    max_steps: usize,
790) -> Result<ArbitraryTrackedIntegerModuleElement, ModuleGroebnerError> {
791    let values = basis
792        .iter()
793        .map(|entry| entry.value.clone())
794        .collect::<Vec<_>>();
795    let (remainder, quotients) =
796        strong_normal_form_arbitrary(&current.value, &values, order, max_steps)?;
797    for (quotient, reducer) in quotients.iter().zip(basis) {
798        if quotient.is_zero() {
799            continue;
800        }
801        current.representation = current
802            .representation
803            .sub(&reducer.representation.scalar_mul(quotient)?)?;
804    }
805    current.value = remainder;
806    Ok(current)
807}
808
809fn normalize_arbitrary_tracked_sign(
810    mut value: ArbitraryTrackedIntegerModuleElement,
811    order: &ModuleMonomialOrder,
812) -> Result<ArbitraryTrackedIntegerModuleElement, ModuleGroebnerError> {
813    if integral_leading_term(&value.value, order)
814        .is_some_and(|(_, coefficient)| coefficient.is_negative())
815    {
816        let negative_one = value.value.module().ring().constant(Integer::from(-1))?;
817        value.value = value.value.scalar_mul(&negative_one)?;
818        value.representation = value.representation.scalar_mul(&negative_one)?;
819    }
820    Ok(value)
821}
822
823fn autoreduce_arbitrary_tracked(
824    basis: &mut Vec<ArbitraryTrackedIntegerModuleElement>,
825    order: &ModuleMonomialOrder,
826    max_steps: usize,
827) -> Result<(), ModuleGroebnerError> {
828    let mut changed = true;
829    let mut passes = 0_usize;
830    while changed && passes <= basis.len().saturating_add(1) {
831        changed = false;
832        let mut index = 0_usize;
833        while index < basis.len() {
834            let reducers = basis
835                .iter()
836                .enumerate()
837                .filter(|(other, _)| *other != index)
838                .map(|(_, entry)| entry.clone())
839                .collect::<Vec<_>>();
840            let reduced = normalize_arbitrary_tracked_sign(
841                tracked_normal_form_arbitrary(basis[index].clone(), &reducers, order, max_steps)?,
842                order,
843            )?;
844            if is_zero(&reduced.value) {
845                basis.remove(index);
846                changed = true;
847            } else {
848                if reduced != basis[index] {
849                    basis[index] = reduced;
850                    changed = true;
851                }
852                index += 1;
853            }
854        }
855        deduplicate_arbitrary_tracked(basis);
856        passes += 1;
857    }
858    if changed {
859        return Err(ModuleGroebnerError::InvalidEvidence(
860            "arbitrary-exponent strong module autoreduction did not stabilize",
861        ));
862    }
863    sort_arbitrary_tracked(basis, order)
864}
865
866fn deduplicate_arbitrary_tracked(basis: &mut Vec<ArbitraryTrackedIntegerModuleElement>) {
867    let mut unique = Vec::with_capacity(basis.len());
868    for entry in basis.drain(..) {
869        if !unique
870            .iter()
871            .any(|known: &ArbitraryTrackedIntegerModuleElement| known.value == entry.value)
872        {
873            unique.push(entry);
874        }
875    }
876    *basis = unique;
877}
878
879fn sort_arbitrary_tracked(
880    basis: &mut [ArbitraryTrackedIntegerModuleElement],
881    order: &ModuleMonomialOrder,
882) -> Result<(), ModuleGroebnerError> {
883    if basis
884        .iter()
885        .any(|entry| integral_leading_term(&entry.value, order).is_none())
886    {
887        return Err(ModuleGroebnerError::InvalidEvidence(
888            "an arbitrary-exponent strong working basis contains zero",
889        ));
890    }
891    basis.sort_by(|left, right| {
892        let (left_term, left_coefficient) =
893            integral_leading_term(&left.value, order).expect("nonzero strong basis invariant");
894        let (right_term, right_coefficient) =
895            integral_leading_term(&right.value, order).expect("nonzero strong basis invariant");
896        compare_module_monomials(&right_term, &left_term, order)
897            .then_with(|| left_coefficient.cmp(&right_coefficient))
898    });
899    Ok(())
900}
901
902fn require_arbitrary_positive_leaders(
903    basis: &[IntegralModuleElement],
904    order: &ModuleMonomialOrder,
905) -> Result<(), ModuleGroebnerError> {
906    if basis.iter().any(|entry| {
907        integral_leading_term(entry, order)
908            .is_none_or(|(_, coefficient)| !coefficient.is_positive())
909    }) {
910        Err(ModuleGroebnerError::InvalidEvidence(
911            "an arbitrary-exponent strong basis has a nonpositive leader",
912        ))
913    } else {
914        Ok(())
915    }
916}
917
918fn verify_arbitrary_strong_module_basis(
919    input: &Submodule<PolynomialRing<IntegerRing>>,
920    inputs: &[IntegralModuleElement],
921    basis: &[ArbitraryTrackedIntegerModuleElement],
922    order: &ModuleMonomialOrder,
923    max_steps: usize,
924) -> Result<(), ModuleGroebnerError> {
925    let values = basis
926        .iter()
927        .map(|entry| entry.value.clone())
928        .collect::<Vec<_>>();
929    require_arbitrary_positive_leaders(&values, order)?;
930    for entry in basis {
931        if entry.representation.module() != input.generator_map().source()
932            || input.generator_map().apply(&entry.representation)? != entry.value
933        {
934            return Err(ModuleGroebnerError::InvalidEvidence(
935                "an arbitrary-exponent strong transformation does not reconstruct its basis value",
936            ));
937        }
938    }
939    for value in inputs {
940        let (remainder, _) = strong_normal_form_arbitrary(value, &values, order, max_steps)?;
941        if !is_zero(&remainder) {
942            return Err(ModuleGroebnerError::InvalidEvidence(
943                "an input does not reduce to zero by the arbitrary-exponent strong basis",
944            ));
945        }
946    }
947    for right in 1..basis.len() {
948        for left in 0..right {
949            if integral_leading_term(&basis[left].value, order)
950                .zip(integral_leading_term(&basis[right].value, order))
951                .is_none_or(|((left, _), (right, _))| left.component() != right.component())
952            {
953                continue;
954            }
955            for pair in arbitrary_integer_module_pairs(&basis[left], &basis[right], order)? {
956                let (remainder, _) =
957                    strong_normal_form_arbitrary(&pair.value, &values, order, max_steps)?;
958                if !is_zero(&remainder) {
959                    return Err(ModuleGroebnerError::InvalidEvidence(
960                        "an arbitrary-exponent strong S/G pair has nonzero remainder",
961                    ));
962                }
963            }
964        }
965    }
966    for index in 0..values.len() {
967        let reducers = values
968            .iter()
969            .enumerate()
970            .filter(|(other, _)| *other != index)
971            .map(|(_, value)| value.clone())
972            .collect::<Vec<_>>();
973        let (remainder, _) =
974            strong_normal_form_arbitrary(&values[index], &reducers, order, max_steps)?;
975        if remainder != values[index] {
976            return Err(ModuleGroebnerError::InvalidEvidence(
977                "the arbitrary-exponent strong basis is not autoreduced",
978            ));
979        }
980    }
981    Ok(())
982}
983
984fn integral_module_term(
985    module: &FreeModule<PolynomialRing<IntegerRing>>,
986    monomial: ModuleMonomial,
987    coefficient: Integer,
988) -> Result<IntegralModuleElement, ModuleGroebnerError> {
989    if monomial.component() >= module.rank() {
990        return Err(ModuleGroebnerError::InvalidEvidence(
991            "an arbitrary-exponent module term lies outside the ambient rank",
992        ));
993    }
994    let mut coordinates = vec![module.ring().zero(); module.rank()];
995    coordinates[monomial.component()] = module
996        .ring()
997        .from_terms([(monomial.exponent().to_vec(), coefficient)])?;
998    element_from_coordinates(module.clone(), coordinates).map_err(Into::into)
999}
1000
1001fn project_strong_module_basis(
1002    input: &Submodule<PolynomialRing<IntegerRing>>,
1003    order: ModuleMonomialOrder,
1004    basis: Vec<groebner_kernel::SparseModuleElement<Integer>>,
1005    transformations: Vec<Vec<poly_core::SparsePolynomial<Integer>>>,
1006) -> Result<StrongModuleGroebnerBasis, ModuleGroebnerError> {
1007    if basis.len() != transformations.len()
1008        || transformations
1009            .iter()
1010            .any(|row| row.len() != input.generator_count())
1011    {
1012        return Err(ModuleGroebnerError::InvalidEvidence(
1013            "the integral module kernel returned an invalid change-matrix shape",
1014        ));
1015    }
1016    let ring = input.ambient().ring();
1017    let basis_values = basis
1018        .into_iter()
1019        .map(|value| from_integer_kernel_module_element(input.ambient(), value))
1020        .collect::<Result<Vec<_>, _>>()?;
1021    let leading_terms = basis_values
1022        .iter()
1023        .map(|value| {
1024            integral_leading_term(value, &order)
1025                .map(|(monomial, coefficient)| StrongModuleTerm::new(monomial, coefficient))
1026                .ok_or(ModuleGroebnerError::InvalidEvidence(
1027                    "the integral strong basis projected a zero generator",
1028                ))
1029        })
1030        .collect::<Result<Vec<_>, _>>()?;
1031    let basis = integral_submodule_from_generators(input.ambient(), basis_values)?;
1032    let transformation_columns = transformations
1033        .into_iter()
1034        .map(|column| {
1035            column
1036                .into_iter()
1037                .map(|polynomial| {
1038                    from_integer_kernel_polynomial(ring, polynomial)
1039                        .map_err(super::map_polynomial_groebner_error)
1040                })
1041                .collect::<Result<Vec<_>, _>>()
1042        })
1043        .collect::<Result<Vec<_>, _>>()?;
1044    let transformation = ModuleHomomorphism::from_coordinate_columns(
1045        basis.generator_map().source().clone(),
1046        input.generator_map().source().clone(),
1047        transformation_columns,
1048    )?;
1049    Ok(StrongModuleGroebnerBasis {
1050        input: input.clone(),
1051        order,
1052        basis,
1053        transformation,
1054        leading_terms,
1055    })
1056}
1057
1058fn strong_relations_submodule(
1059    input: &Submodule<PolynomialRing<IntegerRing>>,
1060) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
1061    let source = input.generator_map().source();
1062    if source.rank() == 0 {
1063        return Submodule::from_generators(source.clone(), Vec::new()).map_err(Into::into);
1064    }
1065    let ring = input.ambient().ring();
1066    let values = map_columns(input.generator_map())?;
1067    let relations = if let Some(kernel_values) =
1068        to_integer_kernel_module_elements(&values, ring, input.ambient().rank())?
1069    {
1070        let output = groebner_kernel::strong_module_relations(
1071            &kernel_values,
1072            &groebner_kernel::StrongGroebnerOptions::default(),
1073        )
1074        .map_err(map_kernel_module_error)?;
1075        output
1076            .generators
1077            .into_iter()
1078            .map(|value| from_integer_kernel_module_element(source, value))
1079            .collect::<Result<Vec<_>, _>>()?
1080    } else {
1081        strong_relations_arbitrary(input)?
1082    };
1083    let result = integral_submodule_from_generators(source, relations)?;
1084    for relation in map_columns(result.generator_map())? {
1085        if !is_zero(&input.generator_map().apply(&relation)?) {
1086            return Err(ModuleGroebnerError::InvalidEvidence(
1087                "an integral graph-elimination relation does not map to zero",
1088            ));
1089        }
1090    }
1091    Ok(result)
1092}
1093
1094fn strong_relations_arbitrary(
1095    input: &Submodule<PolynomialRing<IntegerRing>>,
1096) -> Result<Vec<IntegralModuleElement>, ModuleGroebnerError> {
1097    let source = input.generator_map().source();
1098    let target_rank = input.ambient().rank();
1099    let source_rank = source.rank();
1100    let ring = input.ambient().ring();
1101    let graph_ambient = FreeModule::new(ring.clone(), target_rank.saturating_add(source_rank));
1102    let graph_values = map_columns(input.generator_map())?
1103        .into_iter()
1104        .enumerate()
1105        .map(|(source_component, value)| {
1106            let mut coordinates = value.coordinates().values().collect::<Vec<_>>();
1107            coordinates.extend((0..source_rank).map(|_| ring.zero()));
1108            coordinates[target_rank + source_component] = ring.one();
1109            element_from_coordinates(graph_ambient.clone(), coordinates).map_err(Into::into)
1110        })
1111        .collect::<Result<Vec<_>, ModuleGroebnerError>>()?;
1112    let graph = Submodule::from_generators(graph_ambient.clone(), graph_values)?;
1113    let elimination_order = ModuleMonomialOrder::PositionOverTerm(ring.term_order());
1114    let options = groebner_kernel::StrongGroebnerOptions::default();
1115    let graph_basis = strong_module_basis_arbitrary(&graph, &elimination_order, &options)?;
1116    let mut relations = graph_basis
1117        .into_iter()
1118        .filter(|entry| {
1119            entry
1120                .value
1121                .coordinates()
1122                .values()
1123                .take(target_rank)
1124                .all(|polynomial| polynomial.is_zero())
1125        })
1126        .map(|entry| {
1127            element_from_coordinates(
1128                source.clone(),
1129                entry
1130                    .value
1131                    .coordinates()
1132                    .values()
1133                    .skip(target_rank)
1134                    .collect(),
1135            )
1136            .map_err(Into::into)
1137        })
1138        .collect::<Result<Vec<_>, ModuleGroebnerError>>()?;
1139    if !relations.is_empty() {
1140        let relation_input = Submodule::from_generators(source.clone(), relations)?;
1141        relations = strong_module_basis_arbitrary(&relation_input, &elimination_order, &options)?
1142            .into_iter()
1143            .map(|entry| entry.value)
1144            .collect();
1145    }
1146    for relation in &relations {
1147        if !is_zero(&input.generator_map().apply(relation)?) {
1148            return Err(ModuleGroebnerError::InvalidEvidence(
1149                "an arbitrary-exponent graph-elimination relation does not map to zero",
1150            ));
1151        }
1152    }
1153    Ok(relations)
1154}
1155
1156fn to_integer_kernel_module_elements(
1157    values: &[ModuleElement<PolynomialRing<IntegerRing>>],
1158    ring: &PolynomialRing<IntegerRing>,
1159    rank: usize,
1160) -> Result<Option<Vec<groebner_kernel::SparseModuleElement<Integer>>>, ModuleGroebnerError> {
1161    let Some(order) = crate::commalg::to_kernel_order(&ring.term_order()) else {
1162        return Ok(None);
1163    };
1164    let projected = values
1165        .iter()
1166        .map(|value| {
1167            if value.module().ring() != ring || value.module().rank() != rank {
1168                return Err(ModuleGroebnerError::InvalidEvidence(
1169                    "an integral module value has an incompatible ambient module",
1170                ));
1171            }
1172            let terms = value
1173                .coordinates()
1174                .values()
1175                .enumerate()
1176                .flat_map(|(component, polynomial)| {
1177                    polynomial
1178                        .terms()
1179                        .map(move |(exponent, coefficient)| {
1180                            (component, exponent.to_vec(), coefficient.clone())
1181                        })
1182                        .collect::<Vec<_>>()
1183                })
1184                .map(|(component, exponent, coefficient)| {
1185                    exponent_to_kernel(&exponent).map(|monomial| {
1186                        (
1187                            groebner_kernel::SparseModuleTerm::new(monomial, component),
1188                            coefficient,
1189                        )
1190                    })
1191                })
1192                .collect::<Option<Vec<_>>>();
1193            Ok(terms.map(|terms| {
1194                groebner_kernel::SparseModuleElement::new(
1195                    ring.variables().len(),
1196                    rank,
1197                    order,
1198                    terms,
1199                    &groebner_kernel::IntegerCoefficientRing,
1200                )
1201            }))
1202        })
1203        .collect::<Result<Option<Vec<_>>, ModuleGroebnerError>>()?;
1204    Ok(projected)
1205}
1206
1207fn from_integer_kernel_module_element(
1208    ambient: &FreeModule<PolynomialRing<IntegerRing>>,
1209    value: groebner_kernel::SparseModuleElement<Integer>,
1210) -> Result<ModuleElement<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
1211    if value.rank() != ambient.rank() || value.arity() != ambient.ring().variables().len() {
1212        return Err(ModuleGroebnerError::InvalidEvidence(
1213            "the integral module kernel returned an incompatible value shape",
1214        ));
1215    }
1216    let coordinates = value
1217        .coordinates(&groebner_kernel::IntegerCoefficientRing)
1218        .into_iter()
1219        .map(|polynomial| {
1220            from_integer_kernel_polynomial(ambient.ring(), polynomial)
1221                .map_err(super::map_polynomial_groebner_error)
1222        })
1223        .collect::<Result<Vec<_>, _>>()?;
1224    element_from_coordinates(ambient.clone(), coordinates).map_err(Into::into)
1225}
1226
1227fn validate_integral_order(
1228    module: &FreeModule<PolynomialRing<IntegerRing>>,
1229    order: &ModuleMonomialOrder,
1230) -> Result<(), ModuleGroebnerError> {
1231    if order.base_order() != &module.ring().term_order() {
1232        return Err(ModuleGroebnerError::OrderMismatch);
1233    }
1234    validate_schreyer_frame(order, module.rank(), module.ring().variables().len())
1235}
1236
1237fn integral_leading_term(
1238    value: &ModuleElement<PolynomialRing<IntegerRing>>,
1239    order: &ModuleMonomialOrder,
1240) -> Option<(ModuleMonomial, Integer)> {
1241    let mut leading = None;
1242    for (component, polynomial) in value.coordinates().values().enumerate() {
1243        let Some((exponent, coefficient)) = polynomial.terms().next() else {
1244            continue;
1245        };
1246        let candidate = ModuleMonomial::new(exponent.to_vec(), component);
1247        if leading.as_ref().is_none_or(|(current, _)| {
1248            compare_module_monomials(&candidate, current, order) == core::cmp::Ordering::Greater
1249        }) {
1250            leading = Some((candidate, coefficient.clone()));
1251        }
1252    }
1253    leading
1254}
1255
1256fn integral_submodule_from_generators(
1257    ambient: &FreeModule<PolynomialRing<IntegerRing>>,
1258    generators: Vec<ModuleElement<PolynomialRing<IntegerRing>>>,
1259) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
1260    let ring = ambient.ring();
1261    let source = if let Some(ambient_shifts) = ambient.degree_shifts() {
1262        if ambient.grading_rank() != Some(ring.grading().rank()) {
1263            return Err(ModuleError::GradingMismatch {
1264                operation: "integral homogeneous generator source",
1265            }
1266            .into());
1267        }
1268        let inferred = generators
1269            .iter()
1270            .map(|generator| {
1271                integral_homogeneous_degree(generator, ring.grading(), ambient_shifts)?
1272                    .map(|degree| degree.neg())
1273                    .ok_or(ModuleGroebnerError::InvalidEvidence(
1274                        "an integral strong basis contains zero",
1275                    ))
1276            })
1277            .collect::<Result<Vec<_>, _>>();
1278        match inferred {
1279            Ok(shifts) => FreeModule::new(ring.clone(), generators.len())
1280                .with_degree_shifts(ring.grading().rank(), shifts)?,
1281            Err(_) => FreeModule::new(ring.clone(), generators.len()),
1282        }
1283    } else {
1284        FreeModule::new(ring.clone(), generators.len())
1285    };
1286    let columns = generators
1287        .into_iter()
1288        .map(ModuleElement::into_coordinates)
1289        .collect();
1290    let map = ModuleHomomorphism::from_coordinate_columns(source, ambient.clone(), columns)?;
1291    Submodule::from_generator_map(ambient.clone(), map).map_err(Into::into)
1292}
1293
1294fn integral_homogeneous_degree(
1295    value: &ModuleElement<PolynomialRing<IntegerRing>>,
1296    grading: &FreeAbelianGrading,
1297    shifts: &[crate::commalg::Degree],
1298) -> Result<Option<crate::commalg::Degree>, ModuleGroebnerError> {
1299    let mut homogeneous_degree = None;
1300    for (component, polynomial) in value.coordinates().values().enumerate() {
1301        let shift = shifts
1302            .get(component)
1303            .ok_or(ModuleGroebnerError::InvalidEvidence(
1304                "the ambient integral module has incomplete degree shifts",
1305            ))?;
1306        for (exponent, _) in polynomial.terms() {
1307            let degree = grading
1308                .degree(exponent)
1309                .and_then(|degree| degree.sub(shift))
1310                .map_err(|_| {
1311                    ModuleGroebnerError::InvalidEvidence(
1312                        "an integral module term has an incompatible grading degree",
1313                    )
1314                })?;
1315            match &homogeneous_degree {
1316                None => homogeneous_degree = Some(degree),
1317                Some(known) if known == &degree => {}
1318                Some(_) => {
1319                    return Err(ModuleGroebnerError::InvalidEvidence(
1320                        "the integral module basis is not homogeneous",
1321                    ));
1322                }
1323            }
1324        }
1325    }
1326    Ok(homogeneous_degree)
1327}
1328
1329#[derive(Serialize)]
1330struct StrongModuleGroebnerBasisWireRef<'a> {
1331    #[serde(rename = "type")]
1332    type_id: &'static str,
1333    input: &'a Submodule<PolynomialRing<IntegerRing>>,
1334    order: &'a ModuleMonomialOrder,
1335    basis: &'a Submodule<PolynomialRing<IntegerRing>>,
1336    transformation: &'a ModuleHomomorphism<PolynomialRing<IntegerRing>>,
1337    leading_terms: &'a [StrongModuleTerm],
1338}
1339
1340#[derive(Deserialize)]
1341#[serde(deny_unknown_fields)]
1342struct StrongModuleGroebnerBasisWire {
1343    #[serde(rename = "type")]
1344    type_id: String,
1345    input: Submodule<PolynomialRing<IntegerRing>>,
1346    order: ModuleMonomialOrder,
1347    basis: Submodule<PolynomialRing<IntegerRing>>,
1348    transformation: ModuleHomomorphism<PolynomialRing<IntegerRing>>,
1349    leading_terms: Vec<StrongModuleTerm>,
1350}
1351
1352impl Serialize for StrongModuleGroebnerBasis {
1353    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1354    where
1355        S: Serializer,
1356    {
1357        StrongModuleGroebnerBasisWireRef {
1358            type_id: Self::TYPE_ID,
1359            input: &self.input,
1360            order: &self.order,
1361            basis: &self.basis,
1362            transformation: &self.transformation,
1363            leading_terms: &self.leading_terms,
1364        }
1365        .serialize(serializer)
1366    }
1367}
1368
1369impl<'de> Deserialize<'de> for StrongModuleGroebnerBasis {
1370    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1371    where
1372        D: Deserializer<'de>,
1373    {
1374        let wire = StrongModuleGroebnerBasisWire::deserialize(deserializer)?;
1375        if wire.type_id != Self::TYPE_ID {
1376            return Err(serde::de::Error::custom(format!(
1377                "expected type {}, received {}",
1378                Self::TYPE_ID,
1379                wire.type_id
1380            )));
1381        }
1382        let result = Self {
1383            input: wire.input,
1384            order: wire.order,
1385            basis: wire.basis,
1386            transformation: wire.transformation,
1387            leading_terms: wire.leading_terms,
1388        };
1389        result.verify().map_err(serde::de::Error::custom)?;
1390        Ok(result)
1391    }
1392}
1393
1394#[derive(Serialize)]
1395struct StrongModuleReductionWireRef<'a> {
1396    #[serde(rename = "type")]
1397    type_id: &'static str,
1398    basis: &'a StrongModuleGroebnerBasis,
1399    value: &'a ModuleElement<PolynomialRing<IntegerRing>>,
1400    quotient: &'a ModuleElement<PolynomialRing<IntegerRing>>,
1401    original_combination: &'a ModuleElement<PolynomialRing<IntegerRing>>,
1402    remainder: &'a ModuleElement<PolynomialRing<IntegerRing>>,
1403}
1404
1405#[derive(Deserialize)]
1406#[serde(deny_unknown_fields)]
1407struct StrongModuleReductionWire {
1408    #[serde(rename = "type")]
1409    type_id: String,
1410    basis: StrongModuleGroebnerBasis,
1411    value: ModuleElement<PolynomialRing<IntegerRing>>,
1412    quotient: ModuleElement<PolynomialRing<IntegerRing>>,
1413    original_combination: ModuleElement<PolynomialRing<IntegerRing>>,
1414    remainder: ModuleElement<PolynomialRing<IntegerRing>>,
1415}
1416
1417impl Serialize for StrongModuleReduction {
1418    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1419    where
1420        S: Serializer,
1421    {
1422        StrongModuleReductionWireRef {
1423            type_id: Self::TYPE_ID,
1424            basis: &self.basis,
1425            value: &self.value,
1426            quotient: &self.quotient,
1427            original_combination: &self.original_combination,
1428            remainder: &self.remainder,
1429        }
1430        .serialize(serializer)
1431    }
1432}
1433
1434impl<'de> Deserialize<'de> for StrongModuleReduction {
1435    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1436    where
1437        D: Deserializer<'de>,
1438    {
1439        let wire = StrongModuleReductionWire::deserialize(deserializer)?;
1440        if wire.type_id != Self::TYPE_ID {
1441            return Err(serde::de::Error::custom(format!(
1442                "expected type {}, received {}",
1443                Self::TYPE_ID,
1444                wire.type_id
1445            )));
1446        }
1447        let result = Self {
1448            basis: wire.basis,
1449            value: wire.value,
1450            quotient: wire.quotient,
1451            original_combination: wire.original_combination,
1452            remainder: wire.remainder,
1453        };
1454        result.verify().map_err(serde::de::Error::custom)?;
1455        Ok(result)
1456    }
1457}
1458
1459impl PartialEq for StrongModuleGroebnerBasis {
1460    fn eq(&self, other: &Self) -> bool {
1461        self.input.ambient() == other.input.ambient()
1462            && self.input.generator_map() == other.input.generator_map()
1463            && self.order == other.order
1464            && self.basis.ambient() == other.basis.ambient()
1465            && self.basis.generator_map() == other.basis.generator_map()
1466            && self.transformation == other.transformation
1467            && self.leading_terms == other.leading_terms
1468    }
1469}
1470
1471impl Eq for StrongModuleGroebnerBasis {}
1472
1473impl fmt::Debug for StrongModuleGroebnerBasis {
1474    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1475        formatter
1476            .debug_struct("StrongModuleGroebnerBasis")
1477            .field("input_generators", &self.input.generator_count())
1478            .field("basis_generators", &self.basis.generator_count())
1479            .field("order", &self.order)
1480            .finish()
1481    }
1482}
1483
1484impl fmt::Display for StrongModuleGroebnerBasis {
1485    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1486        write!(
1487            formatter,
1488            "StrongModuleGroebnerBasis(input_generators={}, basis_generators={}, order={})",
1489            self.input.generator_count(),
1490            self.basis.generator_count(),
1491            self.order
1492        )
1493    }
1494}
1495
1496impl SemanticType for StrongModuleGroebnerBasis {
1497    const TYPE_ID: &'static str = "noether/homalg/StrongModuleGroebnerBasis@1";
1498
1499    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1500        Ok(self.to_json()?.into_bytes())
1501    }
1502
1503    fn describe() -> &'static TypeDescription {
1504        &STRONG_MODULE_GROEBNER_BASIS_DESCRIPTION
1505    }
1506}
1507
1508impl PartialEq for StrongModuleReduction {
1509    fn eq(&self, other: &Self) -> bool {
1510        self.basis == other.basis
1511            && self.value == other.value
1512            && self.quotient == other.quotient
1513            && self.original_combination == other.original_combination
1514            && self.remainder == other.remainder
1515    }
1516}
1517
1518impl Eq for StrongModuleReduction {}
1519
1520impl fmt::Debug for StrongModuleReduction {
1521    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1522        formatter
1523            .debug_struct("StrongModuleReduction")
1524            .field("member", &self.is_member())
1525            .finish()
1526    }
1527}
1528
1529impl fmt::Display for StrongModuleReduction {
1530    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1531        write!(
1532            formatter,
1533            "StrongModuleReduction(member={})",
1534            self.is_member()
1535        )
1536    }
1537}
1538
1539impl SemanticType for StrongModuleReduction {
1540    const TYPE_ID: &'static str = "noether/homalg/StrongModuleReduction@1";
1541
1542    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1543        Ok(self.to_json()?.into_bytes())
1544    }
1545
1546    fn describe() -> &'static TypeDescription {
1547        &STRONG_MODULE_REDUCTION_DESCRIPTION
1548    }
1549}
1550
1551#[cfg(test)]
1552mod tests {
1553    use crate::commalg::{MonomialOrder, Polynomial, PolynomialRing};
1554    use crate::{Integer, Z};
1555
1556    use super::*;
1557
1558    fn polynomial(
1559        ring: &PolynomialRing<IntegerRing>,
1560        terms: &[(i64, i64, i64)],
1561    ) -> Polynomial<IntegerRing> {
1562        ring.from_terms(terms.iter().map(|(x, y, coefficient)| {
1563            (
1564                vec![Integer::from(*x), Integer::from(*y)],
1565                Integer::from(*coefficient),
1566            )
1567        }))
1568        .unwrap()
1569    }
1570
1571    fn element(
1572        module: &FreeModule<PolynomialRing<IntegerRing>>,
1573        coordinates: Vec<Polynomial<IntegerRing>>,
1574    ) -> ModuleElement<PolynomialRing<IntegerRing>> {
1575        element_from_coordinates(module.clone(), coordinates).unwrap()
1576    }
1577
1578    #[test]
1579    fn strong_integral_module_basis_reductions_and_lifts_verify() {
1580        let ring =
1581            PolynomialRing::new(Z, ["x", "y"], MonomialOrder::GradedReverseLexicographic).unwrap();
1582        let ambient = FreeModule::new(ring.clone(), 2);
1583        let first = element(&ambient, vec![polynomial(&ring, &[(1, 0, 2)]), ring.zero()]);
1584        let second = element(&ambient, vec![polynomial(&ring, &[(1, 0, 3)]), ring.zero()]);
1585        let third = element(&ambient, vec![ring.zero(), polynomial(&ring, &[(0, 1, 5)])]);
1586        let input =
1587            Submodule::from_generators(ambient.clone(), vec![first.clone(), second.clone(), third])
1588                .unwrap();
1589        let order = ModuleMonomialOrder::TermOverPosition(ring.term_order());
1590        let basis = input.strong_groebner_basis(order.clone()).unwrap();
1591        basis.verify().unwrap();
1592        assert_eq!(basis.leading_terms()[0].coefficient(), &Integer::one());
1593        assert_eq!(
1594            StrongModuleGroebnerBasis::from_json(&basis.to_json().unwrap()).unwrap(),
1595            basis
1596        );
1597        let value = first.add(&second).unwrap();
1598        let reduction = basis.reduce(&value).unwrap();
1599        assert!(reduction.is_member());
1600        reduction.verify().unwrap();
1601        assert_eq!(
1602            StrongModuleReduction::from_json(&reduction.to_json().unwrap()).unwrap(),
1603            reduction
1604        );
1605        assert_eq!(
1606            input
1607                .generator_map()
1608                .apply(reduction.lift().unwrap())
1609                .unwrap(),
1610            value
1611        );
1612
1613        let outside = element(&ambient, vec![ring.zero(), ring.one()]);
1614        assert!(!basis.contains(&outside).unwrap());
1615        assert_eq!(
1616            basis.initial_submodule().unwrap().generator_count(),
1617            basis.basis().generator_count()
1618        );
1619        assert_eq!(
1620            first
1621                .leading_module_term(&order)
1622                .unwrap()
1623                .unwrap()
1624                .coefficient(),
1625            &Integer::from(2)
1626        );
1627    }
1628
1629    #[test]
1630    fn integral_syzygies_and_map_kernel_use_graph_elimination() {
1631        let ring = PolynomialRing::new(Z, ["x", "y"], MonomialOrder::Lexicographic).unwrap();
1632        let ambient = FreeModule::new(ring.clone(), 1);
1633        let first = element(&ambient, vec![polynomial(&ring, &[(1, 0, 2)])]);
1634        let second = element(&ambient, vec![polynomial(&ring, &[(1, 0, 3)])]);
1635        let input =
1636            Submodule::from_generators(ambient, vec![first.clone(), second.clone()]).unwrap();
1637        let order = ModuleMonomialOrder::PositionOverTerm(ring.term_order());
1638        let basis = input.strong_groebner_basis(order.clone()).unwrap();
1639        let relations = basis.syzygies().unwrap();
1640        assert_eq!(relations.generator_count(), 1);
1641        for relation in map_columns(relations.generator_map()).unwrap() {
1642            assert!(is_zero(&input.generator_map().apply(&relation).unwrap()));
1643        }
1644
1645        let kernel = input.generator_map().kernel(order).unwrap();
1646        assert_eq!(kernel.generator_map(), relations.generator_map());
1647    }
1648
1649    #[test]
1650    fn arbitrary_exponent_floor_agrees_with_the_packed_strong_basis() {
1651        let ring = PolynomialRing::new(Z, ["x", "y"], MonomialOrder::Lexicographic).unwrap();
1652        let ambient = FreeModule::new(ring.clone(), 2);
1653        let generators = vec![
1654            element(&ambient, vec![polynomial(&ring, &[(1, 0, 2)]), ring.zero()]),
1655            element(&ambient, vec![polynomial(&ring, &[(1, 0, 3)]), ring.zero()]),
1656            element(&ambient, vec![ring.zero(), polynomial(&ring, &[(0, 1, 5)])]),
1657        ];
1658        let input = Submodule::from_generators(ambient, generators.clone()).unwrap();
1659        let order = ModuleMonomialOrder::TermOverPosition(ring.term_order());
1660        let packed = input.strong_groebner_basis(order.clone()).unwrap();
1661        let arbitrary = project_arbitrary_strong_module_basis(
1662            &input,
1663            order.clone(),
1664            strong_module_basis_arbitrary(
1665                &input,
1666                &order,
1667                &groebner_kernel::StrongGroebnerOptions::default(),
1668            )
1669            .unwrap(),
1670        )
1671        .unwrap();
1672
1673        assert_eq!(
1674            arbitrary.basis().generator_map(),
1675            packed.basis().generator_map()
1676        );
1677        assert_eq!(arbitrary.leading_terms(), packed.leading_terms());
1678        let packed_remainder = packed.normal_form(&generators[0]).unwrap();
1679        let arbitrary_values = map_columns(arbitrary.basis().generator_map()).unwrap();
1680        let (arbitrary_remainder, _) = strong_normal_form_arbitrary(
1681            &generators[0],
1682            &arbitrary_values,
1683            &order,
1684            MAX_REDUCTION_STEPS,
1685        )
1686        .unwrap();
1687        assert_eq!(arbitrary_remainder, packed_remainder);
1688    }
1689
1690    #[test]
1691    fn arbitrary_exponents_keep_strong_bases_reductions_and_relations_exact() {
1692        let ring = PolynomialRing::new(Z, ["x", "y"], MonomialOrder::Lexicographic).unwrap();
1693        let ambient = FreeModule::new(ring.clone(), 2);
1694        let huge = Integer::from(u64::from(u32::MAX) + 1);
1695        let huge_term = |coefficient: i64| {
1696            ring.from_terms([(
1697                vec![huge.clone(), Integer::zero()],
1698                Integer::from(coefficient),
1699            )])
1700            .unwrap()
1701        };
1702        let first = element(&ambient, vec![huge_term(2), ring.zero()]);
1703        let second = element(&ambient, vec![huge_term(3), ring.zero()]);
1704        let third = element(&ambient, vec![ring.zero(), polynomial(&ring, &[(0, 1, 5)])]);
1705        let input =
1706            Submodule::from_generators(ambient.clone(), vec![first.clone(), second.clone(), third])
1707                .unwrap();
1708        let order = ModuleMonomialOrder::TermOverPosition(ring.term_order());
1709
1710        let basis = input.strong_groebner_basis(order.clone()).unwrap();
1711        basis.verify().unwrap();
1712        assert!(basis.leading_terms().iter().any(|leading| {
1713            leading.monomial().exponent() == [huge.clone(), Integer::zero()]
1714                && leading.coefficient() == &Integer::one()
1715        }));
1716
1717        let value = first.add(&second).unwrap();
1718        let reduction = basis.reduce(&value).unwrap();
1719        assert!(reduction.is_member());
1720        reduction.verify().unwrap();
1721        assert_eq!(
1722            input
1723                .generator_map()
1724                .apply(reduction.lift().expect("a member has an exact lift"))
1725                .unwrap(),
1726            value
1727        );
1728        let outside = element(&ambient, vec![ring.one(), ring.zero()]);
1729        assert!(!basis.contains(&outside).unwrap());
1730
1731        let relations = basis.syzygies().unwrap();
1732        assert!(relations.generator_count() > 0);
1733        for relation in map_columns(relations.generator_map()).unwrap() {
1734            assert!(is_zero(&input.generator_map().apply(&relation).unwrap()));
1735        }
1736        let kernel = input.generator_map().kernel(order).unwrap();
1737        assert_eq!(kernel.generator_map(), relations.generator_map());
1738    }
1739}