noether/commalg/
quotient.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Benjamin Antieau and Deven Manam
3//! Canonical commutative quotient-ring contexts and elements.
4
5use core::fmt;
6use core::ops::{Add, Mul, Neg, Sub};
7use std::any::Any;
8use std::sync::Arc;
9
10use serde::ser::SerializeStruct;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use crate::numthy::FiniteField;
14use crate::{
15    CoefficientMap, CommutativeRing, CompositeCoefficientMap, ContextMismatchError, Field, Integer,
16    NoetherError, Ring, SemanticType, TypeDescription, VerificationError,
17};
18
19use super::groebner::{quotient_normal_form, unit_ideal_multipliers};
20use super::{
21    from_kernel_basis, from_prime_kernel_polynomial, to_kernel_order, to_kernel_polynomial,
22    to_prime_kernel_polynomial, Degree, FiniteQuotientAlgebra, FreeAbelianGrading, GroebnerError,
23    HilbertSeries, Ideal, NoetherFieldLane, Polynomial, PolynomialError, PolynomialRing,
24};
25
26static QUOTIENT_RING_DESCRIPTION: TypeDescription = TypeDescription {
27    semantic_id: "noether/commalg/QuotientRing@1",
28    rust_path: "noether::commalg::QuotientRing",
29    python_path: Some("noether.commalg.QuotientRing"),
30};
31
32static QUOTIENT_ELEMENT_DESCRIPTION: TypeDescription = TypeDescription {
33    semantic_id: "noether/commalg/QuotientElement@1",
34    rust_path: "noether::commalg::QuotientElement",
35    python_path: Some("noether.commalg.QuotientElement"),
36};
37
38static QUOTIENT_RING_MAP_DESCRIPTION: TypeDescription = TypeDescription {
39    semantic_id: "noether/commalg/QuotientRingMap@1",
40    rust_path: "noether::commalg::QuotientRingMap",
41    python_path: Some("noether.commalg.QuotientRingMap"),
42};
43const QUOTIENT_RING_MAP_TYPE_ID: &str = "noether/commalg/QuotientRingMap@1";
44
45// Preparation loses on the small monomial quotients but pays for itself on
46// the matched 12-polynomial/55-term reducer. Keep the measured crossover
47// private and conservative until a broader corpus justifies another split.
48const PREPARED_PRIME_MIN_BASIS_POLYNOMIALS: usize = 8;
49const PREPARED_PRIME_MIN_BASIS_TERMS: usize = 32;
50
51/// Failure to inspect a quotient invariant or perform a decidable operation.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub enum QuotientError {
54    /// A value belonged to another ambient or quotient context.
55    ContextMismatch(ContextMismatchError),
56    /// Polynomial grading or arithmetic failed.
57    Polynomial(PolynomialError),
58    /// A defining-ideal computation failed closed.
59    Groebner(GroebnerError),
60    /// A grading-dependent operation was requested from an ungraded quotient.
61    NotGraded,
62    /// The supplied quotient element is not a multiplicative unit.
63    NotAUnit,
64    /// Computed unit evidence did not replay in the quotient.
65    InvalidUnitCertificate,
66}
67
68impl fmt::Display for QuotientError {
69    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Self::ContextMismatch(error) => error.fmt(formatter),
72            Self::Polynomial(error) => error.fmt(formatter),
73            Self::Groebner(error) => error.fmt(formatter),
74            Self::NotGraded => {
75                formatter.write_str("quotient grading requires a multihomogeneous defining ideal")
76            }
77            Self::NotAUnit => formatter.write_str("quotient element is not a unit"),
78            Self::InvalidUnitCertificate => {
79                formatter.write_str("quotient unit certificate failed exact replay")
80            }
81        }
82    }
83}
84
85impl std::error::Error for QuotientError {
86    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
87        match self {
88            Self::ContextMismatch(error) => Some(error),
89            Self::Polynomial(error) => Some(error),
90            Self::Groebner(error) => Some(error),
91            Self::NotGraded | Self::NotAUnit | Self::InvalidUnitCertificate => None,
92        }
93    }
94}
95
96impl From<ContextMismatchError> for QuotientError {
97    fn from(error: ContextMismatchError) -> Self {
98        Self::ContextMismatch(error)
99    }
100}
101
102impl From<PolynomialError> for QuotientError {
103    fn from(error: PolynomialError) -> Self {
104        Self::Polynomial(error)
105    }
106}
107
108impl From<GroebnerError> for QuotientError {
109    fn from(error: GroebnerError) -> Self {
110        Self::Groebner(error)
111    }
112}
113
114/// Failure to construct, apply, compose, or verify an induced quotient-ring map.
115#[derive(Clone, Debug, PartialEq, Eq)]
116pub enum QuotientRingMapError {
117    /// A source, target, or input context was not exactly the declared context.
118    ContextMismatch(ContextMismatchError),
119    /// The checked map between ambient rings failed.
120    BaseMap(String),
121    /// One source defining relation had nonzero image in the target quotient.
122    RelationDoesNotVanish {
123        /// Zero-based position in the source ideal's canonical generator list.
124        generator: usize,
125    },
126}
127
128impl fmt::Display for QuotientRingMapError {
129    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::ContextMismatch(error) => error.fmt(formatter),
132            Self::BaseMap(detail) => write!(formatter, "quotient ambient map failed: {detail}"),
133            Self::RelationDoesNotVanish { generator } => write!(
134                formatter,
135                "source quotient relation {generator} has nonzero image in the target quotient"
136            ),
137        }
138    }
139}
140
141impl std::error::Error for QuotientRingMapError {
142    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143        match self {
144            Self::ContextMismatch(error) => Some(error),
145            Self::BaseMap(_) | Self::RelationDoesNotVanish { .. } => None,
146        }
147    }
148}
149
150impl From<ContextMismatchError> for QuotientRingMapError {
151    fn from(error: ContextMismatchError) -> Self {
152        Self::ContextMismatch(error)
153    }
154}
155
156struct QuotientContext<S: CommutativeRing> {
157    ideal: Ideal<S>,
158    is_graded: bool,
159    reducer: Option<Arc<dyn QuotientReducer<S>>>,
160}
161
162type PreparedQuotientElement = Arc<dyn Any + Send + Sync>;
163
164struct ReducedQuotientElement<E> {
165    value: E,
166    prepared: Option<PreparedQuotientElement>,
167}
168
169trait QuotientReducer<S: CommutativeRing>: Send + Sync {
170    fn normal_form(&self, value: &S::Element) -> Option<ReducedQuotientElement<S::Element>>;
171
172    fn product_normal_form(
173        &self,
174        _left: &S::Element,
175        _right: &S::Element,
176        _left_prepared: Option<&(dyn Any + Send + Sync)>,
177        _right_prepared: Option<&(dyn Any + Send + Sync)>,
178    ) -> Option<ReducedQuotientElement<S::Element>> {
179        None
180    }
181}
182
183struct PreparedFieldPolynomialReducer<R: Field> {
184    ring: PolynomialRing<R>,
185    reducer: groebner_kernel::PreparedPolynomialReducer<R::Element>,
186}
187
188struct PreparedPrimePolynomialReducer<R: Field> {
189    ring: PolynomialRing<R>,
190    field: FiniteField,
191    prime: groebner_kernel::PrimeField32,
192    reducer: groebner_kernel::PreparedPolynomialReducer<u32>,
193}
194
195impl<R: Field> PreparedPrimePolynomialReducer<R> {
196    fn new(ideal: &Ideal<PolynomialRing<R>>) -> Option<Self> {
197        let basis_terms = ideal
198            .generators()
199            .iter()
200            .map(Polynomial::nonzero_term_count)
201            .sum::<usize>();
202        if ideal.generators().len() < PREPARED_PRIME_MIN_BASIS_POLYNOMIALS
203            || basis_terms < PREPARED_PRIME_MIN_BASIS_TERMS
204        {
205            return None;
206        }
207        let field = (ideal.ring().coefficient_ring() as &dyn Any).downcast_ref::<FiniteField>()?;
208        if field.degree() != 1 {
209            return None;
210        }
211        let characteristic = field
212            .characteristic()
213            .to_u64_checked()
214            .and_then(|value| u32::try_from(value).ok())
215            .filter(|value| *value < (1_u32 << 31))?;
216        let prime = groebner_kernel::PrimeField32::new(characteristic).ok()?;
217        let order = to_kernel_order(&ideal.ring().term_order())?;
218        let basis = ideal
219            .generators()
220            .iter()
221            .map(|polynomial| to_prime_kernel_polynomial(polynomial, &prime))
222            .collect::<Option<Vec<_>>>()?;
223        let reducer = groebner_kernel::PreparedPolynomialReducer::new(
224            &prime,
225            ideal.ring().variables().len(),
226            order,
227            &basis,
228        )
229        .ok()?;
230        Some(Self {
231            ring: ideal.ring().clone(),
232            field: field.clone(),
233            prime,
234            reducer,
235        })
236    }
237}
238
239impl<R: Field> QuotientReducer<PolynomialRing<R>> for PreparedPrimePolynomialReducer<R> {
240    fn normal_form(&self, value: &Polynomial<R>) -> Option<ReducedQuotientElement<Polynomial<R>>> {
241        let value = to_prime_kernel_polynomial(value, &self.prime)?;
242        let mut values = self
243            .reducer
244            .normal_forms(
245                &self.prime,
246                &[value],
247                groebner_kernel::GroebnerOptions::default().max_reduction_steps,
248            )
249            .ok()?;
250        let remainder = values.pop()?;
251        let value =
252            from_prime_kernel_polynomial(&self.ring, remainder.clone(), &self.field).ok()?;
253        Some(ReducedQuotientElement {
254            value,
255            prepared: Some(Arc::new(remainder)),
256        })
257    }
258
259    fn product_normal_form(
260        &self,
261        left: &Polynomial<R>,
262        right: &Polynomial<R>,
263        left_prepared: Option<&(dyn Any + Send + Sync)>,
264        right_prepared: Option<&(dyn Any + Send + Sync)>,
265    ) -> Option<ReducedQuotientElement<Polynomial<R>>> {
266        let left_prepared = left_prepared
267            .and_then(|value| value.downcast_ref::<poly_core::SparsePolynomial<u32>>());
268        let right_prepared = right_prepared
269            .and_then(|value| value.downcast_ref::<poly_core::SparsePolynomial<u32>>());
270        let left_owned = left_prepared
271            .is_none()
272            .then(|| to_prime_kernel_polynomial(left, &self.prime))
273            .flatten();
274        let right_owned = right_prepared
275            .is_none()
276            .then(|| to_prime_kernel_polynomial(right, &self.prime))
277            .flatten();
278        let left = left_prepared.or(left_owned.as_ref())?;
279        let right = right_prepared.or(right_owned.as_ref())?;
280        let product = left.multiply(right, &self.prime, None);
281        let mut values = self
282            .reducer
283            .normal_forms(
284                &self.prime,
285                &[product],
286                groebner_kernel::GroebnerOptions::default().max_reduction_steps,
287            )
288            .ok()?;
289        let remainder = values.pop()?;
290        let value =
291            from_prime_kernel_polynomial(&self.ring, remainder.clone(), &self.field).ok()?;
292        Some(ReducedQuotientElement {
293            value,
294            prepared: Some(Arc::new(remainder)),
295        })
296    }
297}
298
299impl<R: Field> PreparedFieldPolynomialReducer<R> {
300    fn new(ideal: &Ideal<PolynomialRing<R>>) -> Option<Self> {
301        // Finite fields already have narrower packed coefficient lanes. Keep
302        // those routes until their prepared variants are installed rather
303        // than replacing them with context-heavy element values.
304        if (ideal.ring().coefficient_ring() as &dyn Any).is::<crate::numthy::FiniteField>() {
305            return None;
306        }
307        let order = to_kernel_order(&ideal.ring().term_order())?;
308        let basis = ideal
309            .generators()
310            .iter()
311            .map(to_kernel_polynomial)
312            .collect::<Option<Vec<_>>>()?;
313        let field = NoetherFieldLane {
314            field: ideal.ring().coefficient_ring(),
315        };
316        let reducer = groebner_kernel::PreparedPolynomialReducer::new(
317            &field,
318            ideal.ring().variables().len(),
319            order,
320            &basis,
321        )
322        .ok()?;
323        Some(Self {
324            ring: ideal.ring().clone(),
325            reducer,
326        })
327    }
328}
329
330impl<R: Field> QuotientReducer<PolynomialRing<R>> for PreparedFieldPolynomialReducer<R> {
331    fn normal_form(&self, value: &Polynomial<R>) -> Option<ReducedQuotientElement<Polynomial<R>>> {
332        let value = to_kernel_polynomial(value)?;
333        let field = NoetherFieldLane {
334            field: self.ring.coefficient_ring(),
335        };
336        let mut values = self
337            .reducer
338            .normal_forms(
339                &field,
340                &[value],
341                groebner_kernel::GroebnerOptions::default().max_reduction_steps,
342            )
343            .ok()?;
344        let remainder = values.pop()?;
345        Some(ReducedQuotientElement {
346            value: from_kernel_basis(&self.ring, vec![remainder]).ok()?.pop()?,
347            prepared: None,
348        })
349    }
350}
351
352/// The exact quotient of one explicit commutative ring by one canonical ideal.
353///
354/// Context identity is the ambient ring presentation together with the exact
355/// defining ideal. The first implemented family is a polynomial ring over an
356/// exact field, but neither the public context nor element name encodes that
357/// specialization.
358#[derive(Clone)]
359pub struct QuotientRing<S: CommutativeRing> {
360    context: Arc<QuotientContext<S>>,
361}
362
363/// An exact quotient element retaining one canonical normal-form lift.
364///
365/// Clones share the immutable quotient context, native polynomial storage, and
366/// any private prepared reducer value. Equality never recomputes a normal form.
367#[derive(Clone)]
368pub struct QuotientElement<S: CommutativeRing> {
369    ring: QuotientRing<S>,
370    lift: S::Element,
371    prepared: Option<PreparedQuotientElement>,
372}
373
374/// A checked map induced between two explicitly presented quotient rings.
375///
376/// `base_map` is a map between the ambient rings. Construction succeeds only
377/// after every canonical source relation maps into the target defining ideal.
378/// Noether never infers an isomorphism or coercion between presentations.
379#[derive(Clone, PartialEq, Eq)]
380pub struct QuotientRingMap<S, T, M>
381where
382    S: CommutativeRing,
383    T: CommutativeRing,
384    M: CoefficientMap<Source = S, Target = T>,
385{
386    source: QuotientRing<S>,
387    target: QuotientRing<T>,
388    base_map: M,
389}
390
391impl<S: CommutativeRing> QuotientRing<S> {
392    /// Ambient commutative ring before taking the quotient.
393    pub fn base_ring(&self) -> &S {
394        self.context.ideal.ring()
395    }
396
397    /// Canonical defining ideal in the ambient ring.
398    pub fn defining_ideal(&self) -> &Ideal<S> {
399        &self.context.ideal
400    }
401
402    fn from_parts(
403        ideal: Ideal<S>,
404        is_graded: bool,
405        reducer: Option<Arc<dyn QuotientReducer<S>>>,
406    ) -> Self {
407        Self {
408            context: Arc::new(QuotientContext {
409                ideal,
410                is_graded,
411                reducer,
412            }),
413        }
414    }
415}
416
417impl<R: Field> PolynomialRing<R> {
418    /// Form the exact quotient by a canonical ideal in this polynomial ring.
419    pub fn quotient(
420        &self,
421        ideal: &Ideal<Self>,
422    ) -> Result<QuotientRing<Self>, ContextMismatchError> {
423        if ideal.ring() != self {
424            return Err(ContextMismatchError {
425                expected: self.to_string(),
426                actual: ideal.ring().to_string(),
427            });
428        }
429        let is_graded = ideal
430            .generators()
431            .iter()
432            .all(|generator| generator.multihomogeneous_degree().ok().flatten().is_some());
433        let reducer = PreparedPrimePolynomialReducer::new(ideal)
434            .map(|reducer| Arc::new(reducer) as Arc<dyn QuotientReducer<Self>>)
435            .or_else(|| {
436                PreparedFieldPolynomialReducer::new(ideal)
437                    .map(|reducer| Arc::new(reducer) as Arc<dyn QuotientReducer<Self>>)
438            });
439        Ok(QuotientRing::from_parts(ideal.clone(), is_graded, reducer))
440    }
441}
442
443impl<R: Field> QuotientRing<PolynomialRing<R>> {
444    fn is_zero_ring_context(&self) -> bool {
445        self.defining_ideal()
446            .generators()
447            .iter()
448            .any(Polynomial::is_one)
449    }
450
451    fn monomial_product(
452        &self,
453        left: &QuotientElement<PolynomialRing<R>>,
454        right: &QuotientElement<PolynomialRing<R>>,
455    ) -> Option<Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError>> {
456        if left.lift.nonzero_term_count() != 1 || right.lift.nonzero_term_count() != 1 {
457            return None;
458        }
459        let (left_exponent, _) = left.lift.terms().next()?;
460        let (right_exponent, _) = right.lift.terms().next()?;
461        let product_exponent = left_exponent
462            .iter()
463            .zip(right_exponent)
464            .map(|(left, right)| left + right)
465            .collect::<Vec<_>>();
466        for relation in self.defining_ideal().generators() {
467            if relation.nonzero_term_count() != 1 {
468                return None;
469            }
470            let (relation_exponent, _) = relation.terms().next()?;
471            if relation_exponent
472                .iter()
473                .zip(&product_exponent)
474                .all(|(relation, product)| relation <= product)
475            {
476                return Some(Ok(self.zero()));
477            }
478        }
479        Some(
480            self.base_ring()
481                .mul(&left.lift, &right.lift)
482                .and_then(|value| self.element_from_normal_form(value)),
483        )
484    }
485
486    /// Project an ambient polynomial to its canonical quotient element.
487    pub fn element(
488        &self,
489        value: &Polynomial<R>,
490    ) -> Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError> {
491        if !self.base_ring().contains(value) {
492            return Err(ContextMismatchError {
493                expected: self.base_ring().to_string(),
494                actual: value.ring().to_string(),
495            });
496        }
497        let reduced = self
498            .context
499            .reducer
500            .as_ref()
501            .and_then(|reducer| reducer.normal_form(value))
502            .map(Ok)
503            .unwrap_or_else(|| {
504                quotient_normal_form(self.defining_ideal(), value).map(|value| {
505                    ReducedQuotientElement {
506                        value,
507                        prepared: None,
508                    }
509                })
510            })?;
511        Ok(QuotientElement {
512            ring: self.clone(),
513            lift: reduced.value,
514            prepared: reduced.prepared,
515        })
516    }
517
518    /// Wrap a lift already proved to be in canonical normal form.
519    ///
520    /// This is crate-private because skipping quotient reduction is only
521    /// sound when the caller has established normality from exact structural
522    /// data, as the monomial module kernels do.
523    pub(crate) fn element_from_normal_form(
524        &self,
525        value: Polynomial<R>,
526    ) -> Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError> {
527        if !self.base_ring().contains(&value) {
528            return Err(ContextMismatchError {
529                expected: self.base_ring().to_string(),
530                actual: value.ring().to_string(),
531            });
532        }
533        Ok(QuotientElement {
534            ring: self.clone(),
535            lift: value,
536            prepared: None,
537        })
538    }
539
540    fn require(
541        &self,
542        value: &QuotientElement<PolynomialRing<R>>,
543    ) -> Result<(), ContextMismatchError> {
544        if self.contains(value) {
545            Ok(())
546        } else {
547            Err(ContextMismatchError {
548                expected: self.to_string(),
549                actual: value.ring.to_string(),
550            })
551        }
552    }
553
554    /// Whether the defining ideal is homogeneous in the complete ambient grading.
555    pub fn is_graded(&self) -> bool {
556        self.context.is_graded
557    }
558
559    /// Ambient grading inherited by this quotient, when the defining ideal is homogeneous.
560    pub fn grading(&self) -> Option<&FreeAbelianGrading> {
561        self.is_graded().then(|| self.base_ring().grading())
562    }
563
564    /// Exact Hilbert series computed from the canonical defining ideal.
565    pub fn hilbert_series(&self) -> Result<HilbertSeries, GroebnerError> {
566        self.defining_ideal().hilbert_series()
567    }
568
569    /// Dense standard-graded Hilbert numerator.
570    pub fn hilbert_numerator(&self) -> Result<Vec<Integer>, GroebnerError> {
571        self.defining_ideal().hilbert_numerator()
572    }
573
574    /// Krull dimension, with `-1` for the zero ring.
575    pub fn dimension(&self) -> Result<i64, GroebnerError> {
576        self.defining_ideal().dimension()
577    }
578
579    /// Standard-graded multiplicity.
580    pub fn degree(&self) -> Result<Integer, GroebnerError> {
581        self.defining_ideal().degree()
582    }
583
584    /// Standard Hilbert polynomial in the canonical variable `m`.
585    pub fn hilbert_polynomial(&self) -> Result<Polynomial<crate::RationalField>, GroebnerError> {
586        self.defining_ideal().hilbert_polynomial()
587    }
588
589    /// Verified standard-monomial basis and multiplication operators for a finite quotient.
590    pub fn finite_algebra(&self) -> Result<FiniteQuotientAlgebra<R>, GroebnerError> {
591        self.defining_ideal().finite_quotient_algebra()
592    }
593
594    /// Descend a checked ambient-ring map to a target quotient.
595    ///
596    /// Construction verifies that every source defining relation maps to zero
597    /// in `target`; failure never creates a partially checked map.
598    pub fn induced_map<Q, M>(
599        &self,
600        target: &QuotientRing<PolynomialRing<Q>>,
601        base_map: M,
602    ) -> Result<QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>, QuotientRingMapError>
603    where
604        Q: Field,
605        M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
606    {
607        QuotientRingMap::new(self.clone(), target.clone(), base_map)
608    }
609
610    fn inverse_element(
611        &self,
612        value: &QuotientElement<PolynomialRing<R>>,
613    ) -> Result<QuotientElement<PolynomialRing<R>>, QuotientError> {
614        self.require(value)?;
615        let generators = self
616            .defining_ideal()
617            .generators()
618            .iter()
619            .cloned()
620            .chain(std::iter::once(value.lift.clone()))
621            .collect::<Vec<_>>();
622        let Some(mut multipliers) = unit_ideal_multipliers(self.base_ring(), &generators)? else {
623            return Err(QuotientError::NotAUnit);
624        };
625        let inverse_lift = multipliers
626            .pop()
627            .ok_or(QuotientError::InvalidUnitCertificate)?;
628        let inverse = self.element(&inverse_lift)?;
629        if self.mul(value, &inverse)? != self.one() {
630            return Err(QuotientError::InvalidUnitCertificate);
631        }
632        Ok(inverse)
633    }
634
635    /// Serialize the complete ambient presentation and canonical defining ideal.
636    pub fn to_json(&self) -> Result<String, NoetherError>
637    where
638        R: Serialize,
639        R::Element: Serialize,
640    {
641        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
642    }
643
644    /// Deserialize and revalidate the ambient ring and canonical defining ideal.
645    pub fn from_json(value: &str) -> Result<Self, NoetherError>
646    where
647        R: for<'de> Deserialize<'de>,
648        R::Element: for<'de> Deserialize<'de>,
649    {
650        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
651    }
652}
653
654impl<R: Field> QuotientElement<PolynomialRing<R>> {
655    /// Exact quotient context containing this element.
656    pub fn ring(&self) -> &QuotientRing<PolynomialRing<R>> {
657        &self.ring
658    }
659
660    /// Explicit canonical normal-form lift to the ambient polynomial ring.
661    pub fn lift(&self) -> &Polynomial<R> {
662        &self.lift
663    }
664
665    /// Whether this is the additive identity of its quotient context.
666    pub fn is_zero(&self) -> bool {
667        self.lift.is_zero()
668    }
669
670    /// Whether this is the multiplicative identity of its quotient context.
671    pub fn is_one(&self) -> bool {
672        self.lift.is_one() || (self.lift.is_zero() && self.ring.is_zero_ring_context())
673    }
674
675    /// Degree of a homogeneous element in the complete quotient grading.
676    pub fn multihomogeneous_degree(&self) -> Result<Option<Degree>, QuotientError> {
677        if !self.ring.is_graded() {
678            return Err(QuotientError::NotGraded);
679        }
680        self.lift.multihomogeneous_degree().map_err(Into::into)
681    }
682
683    /// Decide whether this element is a unit using exact ideal evidence.
684    pub fn is_unit(&self) -> Result<bool, QuotientError> {
685        match self.ring.inverse_element(self) {
686            Ok(_) => Ok(true),
687            Err(QuotientError::NotAUnit) => Ok(false),
688            Err(error) => Err(error),
689        }
690    }
691
692    /// Return the multiplicative inverse when exact ideal evidence proves it exists.
693    pub fn inverse(&self) -> Result<Self, QuotientError> {
694        self.ring.inverse_element(self)
695    }
696
697    /// Raise this quotient element to a nonnegative power.
698    pub fn pow(&self, mut exponent: u64) -> Self {
699        let mut result = self.ring.one();
700        let mut base = self.clone();
701        while exponent != 0 {
702            if exponent & 1 == 1 {
703                result = self
704                    .ring
705                    .mul(&result, &base)
706                    .expect("powers retain one quotient context");
707            }
708            exponent >>= 1;
709            if exponent != 0 {
710                base = self
711                    .ring
712                    .mul(&base, &base)
713                    .expect("powers retain one quotient context");
714            }
715        }
716        result
717    }
718
719    /// Serialize the quotient context and canonical normal-form lift.
720    pub fn to_json(&self) -> Result<String, NoetherError>
721    where
722        R: Serialize,
723        R::Element: Serialize,
724    {
725        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
726    }
727
728    /// Deserialize and reject a noncanonical or context-incompatible lift.
729    pub fn from_json(value: &str) -> Result<Self, NoetherError>
730    where
731        R: for<'de> Deserialize<'de>,
732        R::Element: for<'de> Deserialize<'de>,
733    {
734        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
735    }
736}
737
738impl<R, Q, M> QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
739where
740    R: Field,
741    Q: Field,
742    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
743{
744    /// Construct from exact quotient endpoints and a checked ambient-ring map.
745    pub fn new(
746        source: QuotientRing<PolynomialRing<R>>,
747        target: QuotientRing<PolynomialRing<Q>>,
748        base_map: M,
749    ) -> Result<Self, QuotientRingMapError> {
750        if source.base_ring() != base_map.source() {
751            return Err(ContextMismatchError {
752                expected: source.base_ring().to_string(),
753                actual: base_map.source().to_string(),
754            }
755            .into());
756        }
757        if target.base_ring() != base_map.target() {
758            return Err(ContextMismatchError {
759                expected: target.base_ring().to_string(),
760                actual: base_map.target().to_string(),
761            }
762            .into());
763        }
764        let candidate = Self {
765            source,
766            target,
767            base_map,
768        };
769        candidate.check_relations()?;
770        Ok(candidate)
771    }
772
773    /// Exact source quotient context.
774    pub fn source(&self) -> &QuotientRing<PolynomialRing<R>> {
775        &self.source
776    }
777
778    /// Exact target quotient context.
779    pub fn target(&self) -> &QuotientRing<PolynomialRing<Q>> {
780        &self.target
781    }
782
783    /// Checked map between the two ambient polynomial rings.
784    pub fn base_map(&self) -> &M {
785        &self.base_map
786    }
787
788    /// Apply the descended map and return the target's canonical normal form.
789    pub fn apply(
790        &self,
791        value: &QuotientElement<PolynomialRing<R>>,
792    ) -> Result<QuotientElement<PolynomialRing<Q>>, QuotientRingMapError> {
793        if !self.source.contains(value) {
794            return Err(ContextMismatchError {
795                expected: self.source.to_string(),
796                actual: value.ring().to_string(),
797            }
798            .into());
799        }
800        let image = self
801            .base_map
802            .apply(value.lift())
803            .map_err(|error| QuotientRingMapError::BaseMap(error.to_string()))?;
804        self.target.element(&image).map_err(Into::into)
805    }
806
807    /// Replay endpoint and relation-vanishing checks.
808    pub fn verify(&self) -> Result<(), VerificationError> {
809        if self.source.base_ring() != self.base_map.source() {
810            return Err(VerificationError::InvalidWitness {
811                claim: "quotient map ambient source equals the source quotient's base ring",
812            });
813        }
814        if self.target.base_ring() != self.base_map.target() {
815            return Err(VerificationError::InvalidWitness {
816                claim: "quotient map ambient target equals the target quotient's base ring",
817            });
818        }
819        self.check_relations()
820            .map_err(|_| VerificationError::InvalidWitness {
821                claim: "quotient map sends every source defining relation to zero",
822            })
823    }
824
825    fn check_relations(&self) -> Result<(), QuotientRingMapError> {
826        for (generator, relation) in self.source.defining_ideal().generators().iter().enumerate() {
827            let image = self
828                .base_map
829                .apply(relation)
830                .map_err(|error| QuotientRingMapError::BaseMap(error.to_string()))?;
831            if !self.target.element(&image)?.is_zero() {
832                return Err(QuotientRingMapError::RelationDoesNotVanish { generator });
833            }
834        }
835        Ok(())
836    }
837
838    /// Serialize the quotient endpoints and inducing ambient map.
839    pub fn to_json(&self) -> Result<String, NoetherError>
840    where
841        R: Serialize,
842        R::Element: Serialize,
843        Q: Serialize,
844        Q::Element: Serialize,
845        M: Serialize,
846    {
847        serde_json::to_string(self).map_err(|error| NoetherError::Serialization(error.to_string()))
848    }
849
850    /// Deserialize and replay every endpoint and relation check.
851    pub fn from_json(value: &str) -> Result<Self, NoetherError>
852    where
853        R: for<'de> Deserialize<'de>,
854        R::Element: for<'de> Deserialize<'de>,
855        Q: for<'de> Deserialize<'de>,
856        Q::Element: for<'de> Deserialize<'de>,
857        M: for<'de> Deserialize<'de>,
858    {
859        serde_json::from_str(value).map_err(|error| NoetherError::Serialization(error.to_string()))
860    }
861}
862
863impl<B, C, N> QuotientRingMap<PolynomialRing<B>, PolynomialRing<C>, N>
864where
865    B: Field,
866    C: Field,
867    N: CoefficientMap<Source = PolynomialRing<B>, Target = PolynomialRing<C>> + Clone,
868{
869    /// Compose `self` after another checked quotient map.
870    #[allow(clippy::type_complexity)]
871    pub fn compose<A, M>(
872        &self,
873        inner: &QuotientRingMap<PolynomialRing<A>, PolynomialRing<B>, M>,
874    ) -> Result<
875        QuotientRingMap<PolynomialRing<A>, PolynomialRing<C>, CompositeCoefficientMap<M, N>>,
876        QuotientRingMapError,
877    >
878    where
879        A: Field,
880        M: CoefficientMap<Source = PolynomialRing<A>, Target = PolynomialRing<B>> + Clone,
881    {
882        if inner.target() != self.source() {
883            return Err(ContextMismatchError {
884                expected: self.source().to_string(),
885                actual: inner.target().to_string(),
886            }
887            .into());
888        }
889        let base_map =
890            CompositeCoefficientMap::new(inner.base_map().clone(), self.base_map().clone())?;
891        QuotientRingMap::new(inner.source().clone(), self.target().clone(), base_map)
892    }
893}
894
895impl<R, Q, M> CoefficientMap for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
896where
897    R: Field,
898    Q: Field,
899    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
900{
901    type Source = QuotientRing<PolynomialRing<R>>;
902    type Target = QuotientRing<PolynomialRing<Q>>;
903    type Error = QuotientRingMapError;
904
905    fn source(&self) -> &Self::Source {
906        &self.source
907    }
908
909    fn target(&self) -> &Self::Target {
910        &self.target
911    }
912
913    fn apply(
914        &self,
915        value: &QuotientElement<PolynomialRing<R>>,
916    ) -> Result<QuotientElement<PolynomialRing<Q>>, Self::Error> {
917        QuotientRingMap::apply(self, value)
918    }
919}
920
921impl<R: Field> Ring for QuotientRing<PolynomialRing<R>> {
922    type Element = QuotientElement<PolynomialRing<R>>;
923
924    fn contains(&self, value: &Self::Element) -> bool {
925        self == &value.ring && self.base_ring().contains(&value.lift)
926    }
927
928    fn zero(&self) -> Self::Element {
929        QuotientElement {
930            ring: self.clone(),
931            lift: self.base_ring().zero(),
932            prepared: None,
933        }
934    }
935
936    fn is_zero(&self, value: &Self::Element) -> bool {
937        self.contains(value) && value.is_zero()
938    }
939
940    fn one(&self) -> Self::Element {
941        // A canonical field-polynomial ideal is the unit ideal exactly when
942        // its reduced basis is `[1]`.  In every other quotient the ambient
943        // identity is already a normal form, so constructing it must not run
944        // a fresh normal-form calculation.
945        let lift = if self.is_zero_ring_context() {
946            self.base_ring().zero()
947        } else {
948            self.base_ring().one()
949        };
950        QuotientElement {
951            ring: self.clone(),
952            lift,
953            prepared: None,
954        }
955    }
956
957    fn add(
958        &self,
959        left: &Self::Element,
960        right: &Self::Element,
961    ) -> Result<Self::Element, ContextMismatchError> {
962        self.require(left)?;
963        self.require(right)?;
964        let lift = self.base_ring().add(&left.lift, &right.lift)?;
965        Ok(QuotientElement {
966            ring: self.clone(),
967            lift,
968            prepared: None,
969        })
970    }
971
972    fn sub(
973        &self,
974        left: &Self::Element,
975        right: &Self::Element,
976    ) -> Result<Self::Element, ContextMismatchError> {
977        self.require(left)?;
978        self.require(right)?;
979        let lift = self.base_ring().sub(&left.lift, &right.lift)?;
980        Ok(QuotientElement {
981            ring: self.clone(),
982            lift,
983            prepared: None,
984        })
985    }
986
987    fn mul(
988        &self,
989        left: &Self::Element,
990        right: &Self::Element,
991    ) -> Result<Self::Element, ContextMismatchError> {
992        self.require(left)?;
993        self.require(right)?;
994        if left.lift.is_zero() || right.lift.is_zero() {
995            return Ok(self.zero());
996        }
997        if left.lift.is_one() {
998            return Ok(right.clone());
999        }
1000        if right.lift.is_one() {
1001            return Ok(left.clone());
1002        }
1003        if let Some(product) = self.monomial_product(left, right) {
1004            return product;
1005        }
1006        if let Some(reduced) = self.context.reducer.as_ref().and_then(|reducer| {
1007            reducer.product_normal_form(
1008                &left.lift,
1009                &right.lift,
1010                left.prepared.as_deref(),
1011                right.prepared.as_deref(),
1012            )
1013        }) {
1014            return Ok(QuotientElement {
1015                ring: self.clone(),
1016                lift: reduced.value,
1017                prepared: reduced.prepared,
1018            });
1019        }
1020        self.element(&self.base_ring().mul(&left.lift, &right.lift)?)
1021    }
1022
1023    fn sum_products(
1024        &self,
1025        products: &[(&Self::Element, &Self::Element)],
1026    ) -> Result<Self::Element, ContextMismatchError> {
1027        for &(left, right) in products {
1028            self.require(left)?;
1029            self.require(right)?;
1030        }
1031        let nonzero = products
1032            .iter()
1033            .copied()
1034            .filter(|(left, right)| !left.lift.is_zero() && !right.lift.is_zero())
1035            .collect::<Vec<_>>();
1036        if nonzero.is_empty() {
1037            return Ok(self.zero());
1038        }
1039        if nonzero.len() == 1 {
1040            let (left, right) = nonzero[0];
1041            return self.mul(left, right);
1042        }
1043        let ambient = nonzero
1044            .iter()
1045            .map(|&(left, right)| (&left.lift, &right.lift))
1046            .collect::<Vec<_>>();
1047        self.element(&self.base_ring().sum_products(&ambient)?)
1048    }
1049
1050    fn neg(&self, value: &Self::Element) -> Result<Self::Element, ContextMismatchError> {
1051        self.require(value)?;
1052        Ok(QuotientElement {
1053            ring: self.clone(),
1054            lift: self.base_ring().neg(&value.lift)?,
1055            prepared: None,
1056        })
1057    }
1058}
1059
1060impl<R: Field> CommutativeRing for QuotientRing<PolynomialRing<R>> {}
1061
1062impl<S: CommutativeRing> PartialEq for QuotientRing<S> {
1063    fn eq(&self, other: &Self) -> bool {
1064        Arc::ptr_eq(&self.context, &other.context) || self.context.ideal == other.context.ideal
1065    }
1066}
1067
1068impl<S: CommutativeRing> Eq for QuotientRing<S> {}
1069
1070impl<S: CommutativeRing> fmt::Debug for QuotientRing<S> {
1071    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1072        formatter
1073            .debug_struct("QuotientRing")
1074            .field("base_ring", &S::TYPE_ID)
1075            .field(
1076                "defining_generators",
1077                &self.defining_ideal().generators().len(),
1078            )
1079            .finish()
1080    }
1081}
1082
1083impl<S: CommutativeRing> fmt::Display for QuotientRing<S> {
1084    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1085        write!(
1086            formatter,
1087            "QuotientRing(base={}, generators={})",
1088            S::TYPE_ID,
1089            self.defining_ideal().generators().len()
1090        )
1091    }
1092}
1093
1094impl<S: CommutativeRing> SemanticType for QuotientRing<S> {
1095    const TYPE_ID: &'static str = "noether/commalg/QuotientRing@1";
1096
1097    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1098        self.defining_ideal().canonical_bytes()
1099    }
1100
1101    fn describe() -> &'static TypeDescription {
1102        &QUOTIENT_RING_DESCRIPTION
1103    }
1104}
1105
1106impl<S: CommutativeRing> PartialEq for QuotientElement<S> {
1107    fn eq(&self, other: &Self) -> bool {
1108        self.ring == other.ring && self.lift == other.lift
1109    }
1110}
1111
1112impl<S: CommutativeRing> Eq for QuotientElement<S> {}
1113
1114impl<S: CommutativeRing> fmt::Debug for QuotientElement<S> {
1115    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1116        formatter
1117            .debug_struct("QuotientElement")
1118            .field("base_ring", &S::TYPE_ID)
1119            .finish()
1120    }
1121}
1122
1123impl<S: CommutativeRing> fmt::Display for QuotientElement<S> {
1124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1125        write!(formatter, "QuotientElement(base={})", S::TYPE_ID)
1126    }
1127}
1128
1129impl<S: CommutativeRing> SemanticType for QuotientElement<S> {
1130    const TYPE_ID: &'static str = "noether/commalg/QuotientElement@1";
1131
1132    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1133        let ring = self.ring.canonical_bytes()?;
1134        let lift = self.lift.canonical_bytes()?;
1135        let mut output = Vec::with_capacity(16 + ring.len() + lift.len());
1136        for component in [ring, lift] {
1137            let length = u64::try_from(component.len()).map_err(|_| {
1138                NoetherError::Serialization(
1139                    "quotient-element component is too large for canonical framing".to_owned(),
1140                )
1141            })?;
1142            output.extend_from_slice(&length.to_be_bytes());
1143            output.extend_from_slice(&component);
1144        }
1145        Ok(output)
1146    }
1147
1148    fn describe() -> &'static TypeDescription {
1149        &QUOTIENT_ELEMENT_DESCRIPTION
1150    }
1151}
1152
1153impl<R: Field> Add<&QuotientElement<PolynomialRing<R>>> for &QuotientElement<PolynomialRing<R>> {
1154    type Output = Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError>;
1155
1156    fn add(self, rhs: &QuotientElement<PolynomialRing<R>>) -> Self::Output {
1157        self.ring.add(self, rhs)
1158    }
1159}
1160
1161impl<R: Field> Sub<&QuotientElement<PolynomialRing<R>>> for &QuotientElement<PolynomialRing<R>> {
1162    type Output = Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError>;
1163
1164    fn sub(self, rhs: &QuotientElement<PolynomialRing<R>>) -> Self::Output {
1165        self.ring.sub(self, rhs)
1166    }
1167}
1168
1169impl<R: Field> Mul<&QuotientElement<PolynomialRing<R>>> for &QuotientElement<PolynomialRing<R>> {
1170    type Output = Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError>;
1171
1172    fn mul(self, rhs: &QuotientElement<PolynomialRing<R>>) -> Self::Output {
1173        self.ring.mul(self, rhs)
1174    }
1175}
1176
1177impl<R: Field> Neg for &QuotientElement<PolynomialRing<R>> {
1178    type Output = Result<QuotientElement<PolynomialRing<R>>, ContextMismatchError>;
1179
1180    fn neg(self) -> Self::Output {
1181        self.ring.neg(self)
1182    }
1183}
1184
1185impl<R, Q, M> SemanticType for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
1186where
1187    R: Field,
1188    Q: Field,
1189    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>
1190        + SemanticType
1191        + PartialEq
1192        + Eq,
1193{
1194    const TYPE_ID: &'static str = QUOTIENT_RING_MAP_TYPE_ID;
1195
1196    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1197        let mut output = Vec::new();
1198        for component in [
1199            self.source.canonical_bytes()?,
1200            self.target.canonical_bytes()?,
1201            self.base_map.canonical_bytes()?,
1202        ] {
1203            let length = u64::try_from(component.len()).map_err(|_| {
1204                NoetherError::Serialization(
1205                    "quotient-ring-map component is too large for canonical framing".to_owned(),
1206                )
1207            })?;
1208            output.extend_from_slice(&length.to_be_bytes());
1209            output.extend_from_slice(&component);
1210        }
1211        Ok(output)
1212    }
1213
1214    fn describe() -> &'static TypeDescription {
1215        &QUOTIENT_RING_MAP_DESCRIPTION
1216    }
1217}
1218
1219impl<R, Q, M> fmt::Debug for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
1220where
1221    R: Field,
1222    Q: Field,
1223    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
1224{
1225    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1226        formatter
1227            .debug_struct("QuotientRingMap")
1228            .field("source", &self.source)
1229            .field("target", &self.target)
1230            .finish_non_exhaustive()
1231    }
1232}
1233
1234impl<R, Q, M> fmt::Display for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
1235where
1236    R: Field,
1237    Q: Field,
1238    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
1239{
1240    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1241        write!(formatter, "{} -> {}", self.source, self.target)
1242    }
1243}
1244
1245#[derive(Deserialize)]
1246#[serde(
1247    deny_unknown_fields,
1248    bound(
1249        deserialize = "R: Field + Deserialize<'de>, R::Element: Deserialize<'de>, Q: Field + Deserialize<'de>, Q::Element: Deserialize<'de>, M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>> + Deserialize<'de>"
1250    )
1251)]
1252struct QuotientRingMapWire<R, Q, M>
1253where
1254    R: Field,
1255    Q: Field,
1256    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>>,
1257{
1258    #[serde(rename = "type")]
1259    type_id: String,
1260    source: QuotientRing<PolynomialRing<R>>,
1261    target: QuotientRing<PolynomialRing<Q>>,
1262    base_map: M,
1263}
1264
1265impl<R, Q, M> Serialize for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
1266where
1267    R: Field + Serialize,
1268    R::Element: Serialize,
1269    Q: Field + Serialize,
1270    Q::Element: Serialize,
1271    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>> + Serialize,
1272{
1273    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1274    where
1275        S: Serializer,
1276    {
1277        let mut state = serializer.serialize_struct("QuotientRingMap", 4)?;
1278        state.serialize_field("type", QUOTIENT_RING_MAP_TYPE_ID)?;
1279        state.serialize_field("source", &self.source)?;
1280        state.serialize_field("target", &self.target)?;
1281        state.serialize_field("base_map", &self.base_map)?;
1282        state.end()
1283    }
1284}
1285
1286impl<'de, R, Q, M> Deserialize<'de> for QuotientRingMap<PolynomialRing<R>, PolynomialRing<Q>, M>
1287where
1288    R: Field + Deserialize<'de>,
1289    R::Element: Deserialize<'de>,
1290    Q: Field + Deserialize<'de>,
1291    Q::Element: Deserialize<'de>,
1292    M: CoefficientMap<Source = PolynomialRing<R>, Target = PolynomialRing<Q>> + Deserialize<'de>,
1293{
1294    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1295    where
1296        D: Deserializer<'de>,
1297    {
1298        let wire = QuotientRingMapWire::<R, Q, M>::deserialize(deserializer)?;
1299        if wire.type_id != QUOTIENT_RING_MAP_TYPE_ID {
1300            return Err(serde::de::Error::custom(format!(
1301                "expected type {QUOTIENT_RING_MAP_TYPE_ID}, received {}",
1302                wire.type_id
1303            )));
1304        }
1305        Self::new(wire.source, wire.target, wire.base_map).map_err(serde::de::Error::custom)
1306    }
1307}
1308
1309#[derive(Deserialize)]
1310#[serde(
1311    deny_unknown_fields,
1312    bound(deserialize = "R: Field + Deserialize<'de>, R::Element: Deserialize<'de>")
1313)]
1314struct QuotientRingWire<R: Field> {
1315    #[serde(rename = "type")]
1316    type_id: String,
1317    defining_ideal: Ideal<PolynomialRing<R>>,
1318}
1319
1320impl<R> Serialize for QuotientRing<PolynomialRing<R>>
1321where
1322    R: Field + Serialize,
1323    R::Element: Serialize,
1324{
1325    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1326    where
1327        S: Serializer,
1328    {
1329        let mut state = serializer.serialize_struct("QuotientRing", 2)?;
1330        state.serialize_field("type", Self::TYPE_ID)?;
1331        state.serialize_field("defining_ideal", self.defining_ideal())?;
1332        state.end()
1333    }
1334}
1335
1336impl<'de, R> Deserialize<'de> for QuotientRing<PolynomialRing<R>>
1337where
1338    R: Field + Deserialize<'de>,
1339    R::Element: Deserialize<'de>,
1340{
1341    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1342    where
1343        D: Deserializer<'de>,
1344    {
1345        let wire = QuotientRingWire::<R>::deserialize(deserializer)?;
1346        if wire.type_id != Self::TYPE_ID {
1347            return Err(serde::de::Error::custom(format!(
1348                "expected type {}, received {}",
1349                Self::TYPE_ID,
1350                wire.type_id
1351            )));
1352        }
1353        wire.defining_ideal
1354            .ring()
1355            .quotient(&wire.defining_ideal)
1356            .map_err(serde::de::Error::custom)
1357    }
1358}
1359
1360#[derive(Deserialize)]
1361#[serde(
1362    deny_unknown_fields,
1363    bound(deserialize = "R: Field + Deserialize<'de>, R::Element: Deserialize<'de>")
1364)]
1365struct QuotientElementWire<R: Field> {
1366    #[serde(rename = "type")]
1367    type_id: String,
1368    ring: QuotientRing<PolynomialRing<R>>,
1369    lift: Polynomial<R>,
1370}
1371
1372impl<R> Serialize for QuotientElement<PolynomialRing<R>>
1373where
1374    R: Field + Serialize,
1375    R::Element: Serialize,
1376{
1377    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1378    where
1379        S: Serializer,
1380    {
1381        let mut state = serializer.serialize_struct("QuotientElement", 3)?;
1382        state.serialize_field("type", Self::TYPE_ID)?;
1383        state.serialize_field("ring", &self.ring)?;
1384        state.serialize_field("lift", &self.lift)?;
1385        state.end()
1386    }
1387}
1388
1389impl<'de, R> Deserialize<'de> for QuotientElement<PolynomialRing<R>>
1390where
1391    R: Field + Deserialize<'de>,
1392    R::Element: Deserialize<'de>,
1393{
1394    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1395    where
1396        D: Deserializer<'de>,
1397    {
1398        let wire = QuotientElementWire::<R>::deserialize(deserializer)?;
1399        if wire.type_id != Self::TYPE_ID {
1400            return Err(serde::de::Error::custom(format!(
1401                "expected type {}, received {}",
1402                Self::TYPE_ID,
1403                wire.type_id
1404            )));
1405        }
1406        let value = wire
1407            .ring
1408            .element(&wire.lift)
1409            .map_err(serde::de::Error::custom)?;
1410        if value.lift != wire.lift {
1411            return Err(serde::de::Error::custom(
1412                "serialized quotient lift is not the canonical normal form",
1413            ));
1414        }
1415        Ok(value)
1416    }
1417}