1use 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#[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 pub fn monomial(&self) -> &ModuleMonomial {
73 &self.monomial
74 }
75
76 pub fn coefficient(&self) -> &Integer {
78 &self.coefficient
79 }
80
81 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 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#[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#[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 pub fn input(&self) -> &Submodule<PolynomialRing<IntegerRing>> {
170 &self.input
171 }
172
173 pub fn order(&self) -> &ModuleMonomialOrder {
175 &self.order
176 }
177
178 pub fn basis(&self) -> &Submodule<PolynomialRing<IntegerRing>> {
180 &self.basis
181 }
182
183 pub fn transformation(&self) -> &ModuleHomomorphism<PolynomialRing<IntegerRing>> {
185 &self.transformation
186 }
187
188 pub fn leading_terms(&self) -> &[StrongModuleTerm] {
190 &self.leading_terms
191 }
192
193 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 pub const fn is_complete(&self) -> bool {
217 true
218 }
219
220 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("ient)?;
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 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 pub fn contains(
296 &self,
297 value: &ModuleElement<PolynomialRing<IntegerRing>>,
298 ) -> Result<bool, ModuleGroebnerError> {
299 Ok(self.reduce(value)?.is_member())
300 }
301
302 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 pub fn syzygies(&self) -> Result<Submodule<PolynomialRing<IntegerRing>>, ModuleGroebnerError> {
315 strong_relations_submodule(&self.input)
316 }
317
318 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 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 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 pub fn basis(&self) -> &StrongModuleGroebnerBasis {
358 &self.basis
359 }
360
361 pub fn value(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
363 &self.value
364 }
365
366 pub fn quotient(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
368 &self.quotient
369 }
370
371 pub fn original_combination(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
373 &self.original_combination
374 }
375
376 pub fn remainder(&self) -> &ModuleElement<PolynomialRing<IntegerRing>> {
378 &self.remainder
379 }
380
381 pub fn is_member(&self) -> bool {
383 is_zero(&self.remainder)
384 }
385
386 pub fn lift(&self) -> Option<&ModuleElement<PolynomialRing<IntegerRing>>> {
388 self.is_member().then_some(&self.original_combination)
389 }
390
391 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 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 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 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 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 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("ients[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(¤t.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 == °ree => {}
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}