noether/commalg/
curve.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Benjamin Antieau and Deven Manam
3//! Checked curves and canonical algebraic function fields.
4
5use core::fmt;
6use core::ops::{Add, Div, Mul, Neg, Sub};
7
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use crate::linalg::{LinalgError, Matrix};
12use crate::numthy::{FiniteField, NumberField};
13use crate::{
14    CommutativeRing, ContextMismatchError, DivisionByZeroError, Field, Integer, IntegralDomain,
15    InversionError, NoetherError, RationalField, Ring, SemanticType, TypeDescription, UnitError,
16    UnitInversion, VerificationError,
17};
18
19use super::scheme::projective_closed_subscheme;
20use super::MonomialOrder;
21use super::{
22    GroebnerError, Polynomial, PolynomialContextError, PolynomialError, PolynomialFactorization,
23    PolynomialFactorizationError, PolynomialRing, PrimeIdeal, ProjectiveSpace,
24    RationalFunctionError, RationalFunctionField, Scheme, SchemeError, SchemePoint,
25};
26
27static CURVE_DESCRIPTION: TypeDescription = TypeDescription {
28    semantic_id: "noether/commalg/Curve@1",
29    rust_path: "noether::commalg::Curve",
30    python_path: Some("noether.commalg.Curve"),
31};
32
33static CURVE_FUNCTION_FIELD_DESCRIPTION: TypeDescription = TypeDescription {
34    semantic_id: "noether/commalg/CurveFunctionField@1",
35    rust_path: "noether::commalg::CurveFunctionField",
36    python_path: Some("noether.commalg.CurveFunctionField"),
37};
38
39static CURVE_FUNCTION_DESCRIPTION: TypeDescription = TypeDescription {
40    semantic_id: "noether/commalg/CurveFunction@1",
41    rust_path: "noether::commalg::CurveFunction",
42    python_path: Some("noether.commalg.CurveFunction"),
43};
44
45static CURVE_POINT_DESCRIPTION: TypeDescription = TypeDescription {
46    semantic_id: "noether/commalg/CurvePoint@1",
47    rust_path: "noether::commalg::CurvePoint",
48    python_path: Some("noether.commalg.CurvePoint"),
49};
50
51static CURVE_MORPHISM_DESCRIPTION: TypeDescription = TypeDescription {
52    semantic_id: "noether/commalg/CurveMorphism@1",
53    rust_path: "noether::commalg::CurveMorphism",
54    python_path: Some("noether.commalg.CurveMorphism"),
55};
56
57static CURVE_CLOSED_POINT_DESCRIPTION: TypeDescription = TypeDescription {
58    semantic_id: "noether/commalg/CurveClosedPoint@1",
59    rust_path: "noether::commalg::CurveClosedPoint",
60    python_path: Some("noether.commalg.CurveClosedPoint"),
61};
62
63static WEIL_DIVISOR_DESCRIPTION: TypeDescription = TypeDescription {
64    semantic_id: "noether/commalg/WeilDivisor@1",
65    rust_path: "noether::commalg::WeilDivisor",
66    python_path: Some("noether.commalg.WeilDivisor"),
67};
68
69static RIEMANN_ROCH_SPACE_DESCRIPTION: TypeDescription = TypeDescription {
70    semantic_id: "noether/commalg/RiemannRochSpace@1",
71    rust_path: "noether::commalg::RiemannRochSpace",
72    python_path: Some("noether.commalg.RiemannRochSpace"),
73};
74
75/// Failure to construct or operate on a checked curve or its function field.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum CurveError {
78    /// The projective scheme layer rejected the presentation or a point.
79    Scheme(SchemeError),
80    /// A polynomial context could not be constructed.
81    PolynomialContext(PolynomialContextError),
82    /// Exact polynomial arithmetic failed.
83    Polynomial(PolynomialError),
84    /// Exact ideal arithmetic failed.
85    Groebner(GroebnerError),
86    /// Exact linear algebra for a section space failed.
87    LinearAlgebra(LinalgError),
88    /// Rational-function arithmetic in the chosen transcendence coordinate failed.
89    RationalFunction(RationalFunctionError),
90    /// A univariate intersection polynomial could not be factored exactly.
91    Factorization(PolynomialFactorizationError),
92    /// Values belonged to unequal curve or function-field contexts.
93    ContextMismatch(ContextMismatchError),
94    /// A plane-curve equation was not a nonzero homogeneous polynomial in three variables.
95    InvalidPlaneEquation,
96    /// The supplied plane equation did not cut out a projective curve.
97    NotOneDimensional,
98    /// The exact projective Jacobian criterion found a singular geometric point.
99    SingularCurve,
100    /// A Weierstrass equation had zero discriminant.
101    SingularWeierstrassEquation,
102    /// No deterministic affine presentation admitted a finite extension of `K(x)`.
103    NoFunctionFieldPresentation,
104    /// Division by the zero curve function was attempted.
105    DivisionByZero,
106    /// A bounded local expansion did not expose the first nonzero coefficient.
107    LocalExpansionLimit {
108        /// Maximum order computed in the chosen local parameter.
109        limit: usize,
110    },
111    /// An exact curve algorithm reached a declared finite resource bound.
112    ResourceLimit {
113        /// Resource whose bound was reached.
114        resource: &'static str,
115        /// Declared bound.
116        limit: usize,
117    },
118    /// A serialized or recomputed witness did not verify.
119    InvalidWitness(&'static str),
120}
121
122impl fmt::Display for CurveError {
123    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
124        match self {
125            Self::Scheme(error) => error.fmt(formatter),
126            Self::PolynomialContext(error) => error.fmt(formatter),
127            Self::Polynomial(error) => error.fmt(formatter),
128            Self::Groebner(error) => error.fmt(formatter),
129            Self::LinearAlgebra(error) => error.fmt(formatter),
130            Self::RationalFunction(error) => error.fmt(formatter),
131            Self::Factorization(error) => error.fmt(formatter),
132            Self::ContextMismatch(error) => error.fmt(formatter),
133            Self::InvalidPlaneEquation => formatter.write_str(
134                "a smooth projective plane curve needs one nonzero standard-homogeneous equation in three variables",
135            ),
136            Self::NotOneDimensional => {
137                formatter.write_str("the supplied projective equation does not have dimension one")
138            }
139            Self::SingularCurve => formatter
140                .write_str("the projective plane curve fails the exact Jacobian smoothness test"),
141            Self::SingularWeierstrassEquation => {
142                formatter.write_str("the Weierstrass equation has zero discriminant")
143            }
144            Self::NoFunctionFieldPresentation => formatter.write_str(
145                "no deterministic affine chart presents the curve as a finite extension of K(x)",
146            ),
147            Self::DivisionByZero => formatter.write_str("division by zero curve function"),
148            Self::LocalExpansionLimit { limit } => write!(
149                formatter,
150                "the local curve expansion did not determine a value through order {limit}"
151            ),
152            Self::ResourceLimit { resource, limit } => {
153                write!(formatter, "curve computation exceeded its {resource} limit of {limit}")
154            }
155            Self::InvalidWitness(claim) => write!(formatter, "invalid curve witness: {claim}"),
156        }
157    }
158}
159
160impl std::error::Error for CurveError {
161    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
162        match self {
163            Self::Scheme(error) => Some(error),
164            Self::PolynomialContext(error) => Some(error),
165            Self::Polynomial(error) => Some(error),
166            Self::Groebner(error) => Some(error),
167            Self::LinearAlgebra(error) => Some(error),
168            Self::RationalFunction(error) => Some(error),
169            Self::Factorization(error) => Some(error),
170            Self::ContextMismatch(error) => Some(error),
171            Self::InvalidPlaneEquation
172            | Self::NotOneDimensional
173            | Self::SingularCurve
174            | Self::SingularWeierstrassEquation
175            | Self::NoFunctionFieldPresentation
176            | Self::DivisionByZero
177            | Self::LocalExpansionLimit { .. }
178            | Self::ResourceLimit { .. }
179            | Self::InvalidWitness(_) => None,
180        }
181    }
182}
183
184impl From<SchemeError> for CurveError {
185    fn from(value: SchemeError) -> Self {
186        Self::Scheme(value)
187    }
188}
189
190impl From<PolynomialContextError> for CurveError {
191    fn from(value: PolynomialContextError) -> Self {
192        Self::PolynomialContext(value)
193    }
194}
195
196impl From<PolynomialError> for CurveError {
197    fn from(value: PolynomialError) -> Self {
198        Self::Polynomial(value)
199    }
200}
201
202impl From<GroebnerError> for CurveError {
203    fn from(value: GroebnerError) -> Self {
204        Self::Groebner(value)
205    }
206}
207
208impl From<LinalgError> for CurveError {
209    fn from(value: LinalgError) -> Self {
210        Self::LinearAlgebra(value)
211    }
212}
213
214impl From<RationalFunctionError> for CurveError {
215    fn from(value: RationalFunctionError) -> Self {
216        Self::RationalFunction(value)
217    }
218}
219
220impl From<PolynomialFactorizationError> for CurveError {
221    fn from(value: PolynomialFactorizationError) -> Self {
222        Self::Factorization(value)
223    }
224}
225
226impl From<ContextMismatchError> for CurveError {
227    fn from(value: ContextMismatchError) -> Self {
228        Self::ContextMismatch(value)
229    }
230}
231
232/// The admitted checked presentation retained by a curve value.
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub enum CurvePresentationKind {
235    /// One smooth homogeneous equation in the projective plane.
236    SmoothProjectivePlane,
237    /// A nonsingular projective generalized Weierstrass equation.
238    SmoothProjectiveWeierstrass,
239}
240
241/// Replayable numerical evidence for the plane-curve geometry claim.
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct CurveSmoothnessEvidence {
244    quotient_dimension: i64,
245    singular_cone_dimension: i64,
246}
247
248impl CurveSmoothnessEvidence {
249    /// Krull dimension of the homogeneous coordinate-ring quotient.
250    pub fn quotient_dimension(&self) -> i64 {
251        self.quotient_dimension
252    }
253
254    /// Dimension of the affine singular cone; at most zero means empty in `Proj`.
255    pub fn singular_cone_dimension(&self) -> i64 {
256        self.singular_cone_dimension
257    }
258}
259
260#[derive(Clone, PartialEq, Eq)]
261enum CurvePresentation<R: Field> {
262    Plane,
263    Weierstrass { coefficients: [R::Element; 5] },
264}
265
266/// A checked smooth projective integral curve in an admitted exact presentation.
267///
268/// The initial native presentation is a smooth projective plane equation. A
269/// generalized Weierstrass presentation is retained as a distinguished plane
270/// model so later elliptic-curve operations can recover its coefficients
271/// without pattern matching a polynomial.
272#[derive(Clone, PartialEq, Eq)]
273pub struct Curve<R: Field> {
274    scheme: Scheme<R>,
275    equation: Polynomial<R>,
276    presentation: CurvePresentation<R>,
277    evidence: CurveSmoothnessEvidence,
278}
279
280impl<R: Field> Curve<R> {
281    /// Check one homogeneous plane equation by exact dimension and Jacobian tests.
282    pub fn smooth_projective_plane(equation: Polynomial<R>) -> Result<Self, CurveError> {
283        Self::from_plane_equation(equation, CurvePresentation::Plane)
284    }
285
286    /// Construct and check a generalized Weierstrass curve with coordinates `(x,y,z)`.
287    pub fn smooth_projective_weierstrass(
288        base: R,
289        coefficients: [R::Element; 5],
290    ) -> Result<Self, CurveError> {
291        if coefficients
292            .iter()
293            .any(|coefficient| !base.contains(coefficient))
294        {
295            return Err(ContextMismatchError {
296                expected: R::TYPE_ID.to_owned(),
297                actual: "Weierstrass coefficient from another field context".to_owned(),
298            }
299            .into());
300        }
301        if weierstrass_discriminant(&base, &coefficients)? == base.zero() {
302            return Err(CurveError::SingularWeierstrassEquation);
303        }
304        let ring = PolynomialRing::new(
305            base.clone(),
306            ["x", "y", "z"],
307            MonomialOrder::GradedReverseLexicographic,
308        )?;
309        let one = base.one();
310        let minus_one = base.neg(&one)?;
311        let equation = ring.from_terms([
312            (small_exponents([0, 2, 1]), one),
313            (small_exponents([1, 1, 1]), coefficients[0].clone()),
314            (small_exponents([0, 1, 2]), coefficients[2].clone()),
315            (small_exponents([3, 0, 0]), minus_one),
316            (small_exponents([2, 0, 1]), base.neg(&coefficients[1])?),
317            (small_exponents([1, 0, 2]), base.neg(&coefficients[3])?),
318            (small_exponents([0, 0, 3]), base.neg(&coefficients[4])?),
319        ])?;
320        Self::from_plane_equation(equation, CurvePresentation::Weierstrass { coefficients })
321    }
322
323    fn from_plane_equation(
324        equation: Polynomial<R>,
325        presentation: CurvePresentation<R>,
326    ) -> Result<Self, CurveError> {
327        let ring = equation.ring().clone();
328        if ring.variables().len() != 3
329            || equation.is_zero()
330            || equation.homogeneous_degree()?.is_none()
331            || !ring.grading().is_standard()
332        {
333            return Err(CurveError::InvalidPlaneEquation);
334        }
335        let ideal = ring.ideal([equation])?;
336        if ideal.dimension()? != 2 {
337            return Err(CurveError::NotOneDimensional);
338        }
339        let equation = ideal
340            .generators()
341            .first()
342            .cloned()
343            .ok_or(CurveError::InvalidPlaneEquation)?;
344        let mut singular_generators = Vec::with_capacity(4);
345        singular_generators.push(equation.clone());
346        for variable in 0..3 {
347            singular_generators.push(equation.derivative(variable)?);
348        }
349        let singular_cone_dimension = ring.ideal(singular_generators)?.dimension()?;
350        if singular_cone_dimension > 0 {
351            return Err(CurveError::SingularCurve);
352        }
353        let ambient = ProjectiveSpace::from_coordinate_ring(ring.clone())?;
354        let scheme = projective_closed_subscheme(&ambient, ideal)?;
355        Ok(Self {
356            scheme,
357            equation,
358            presentation,
359            evidence: CurveSmoothnessEvidence {
360                quotient_dimension: 2,
361                singular_cone_dimension,
362            },
363        })
364    }
365
366    /// Base coefficient field.
367    pub fn base(&self) -> &R {
368        self.scheme.base()
369    }
370
371    /// Checked underlying projective scheme.
372    pub fn scheme(&self) -> &Scheme<R> {
373        &self.scheme
374    }
375
376    /// Canonical homogeneous plane equation.
377    pub fn equation(&self) -> &Polynomial<R> {
378        &self.equation
379    }
380
381    /// Admitted presentation class.
382    pub fn presentation_kind(&self) -> CurvePresentationKind {
383        match self.presentation {
384            CurvePresentation::Plane => CurvePresentationKind::SmoothProjectivePlane,
385            CurvePresentation::Weierstrass { .. } => {
386                CurvePresentationKind::SmoothProjectiveWeierstrass
387            }
388        }
389    }
390
391    /// Exact dimension and Jacobian evidence retained at construction.
392    pub fn smoothness_evidence(&self) -> &CurveSmoothnessEvidence {
393        &self.evidence
394    }
395
396    /// Generalized Weierstrass coefficients `(a1,a2,a3,a4,a6)`, when distinguished.
397    pub fn weierstrass_coefficients(&self) -> Option<&[R::Element; 5]> {
398        match &self.presentation {
399            CurvePresentation::Plane => None,
400            CurvePresentation::Weierstrass { coefficients } => Some(coefficients),
401        }
402    }
403
404    /// Discriminant of the distinguished generalized Weierstrass model.
405    ///
406    /// A plain smooth plane presentation has no distinguished Weierstrass
407    /// coefficients and therefore returns `None`.
408    pub fn weierstrass_discriminant(&self) -> Result<Option<R::Element>, CurveError> {
409        self.weierstrass_coefficients()
410            .map(|coefficients| weierstrass_discriminant(self.base(), coefficients))
411            .transpose()
412    }
413
414    /// Construct the canonical algebraic function field in a deterministic affine chart.
415    pub fn function_field(&self) -> Result<CurveFunctionField<R>, CurveError> {
416        CurveFunctionField::new(self.clone())
417    }
418
419    /// Validate one rational point on this curve.
420    pub fn point<I>(&self, coordinates: I) -> Result<CurvePoint<R>, CurveError>
421    where
422        I: IntoIterator<Item = R::Element>,
423    {
424        let point = self.scheme.point(coordinates)?;
425        Ok(CurvePoint {
426            curve: self.clone(),
427            point,
428        })
429    }
430
431    /// Recompute the checked presentation and compare its canonical equation and evidence.
432    pub fn verify(&self) -> Result<(), VerificationError> {
433        let rebuilt = Self::from_plane_equation(self.equation.clone(), self.presentation.clone())
434            .map_err(|_| VerificationError::InvalidWitness {
435            claim: "curve presentation passes dimension and Jacobian checks",
436        })?;
437        if &rebuilt == self {
438            Ok(())
439        } else {
440            Err(VerificationError::InvalidWitness {
441                claim: "curve presentation and smoothness evidence recompute canonically",
442            })
443        }
444    }
445
446    /// Serialize the checked presentation and its exact coefficient context.
447    pub fn to_json(&self) -> Result<String, NoetherError>
448    where
449        R: Serialize,
450        R::Element: Serialize,
451    {
452        let coefficients = match &self.presentation {
453            CurvePresentation::Plane => serde_json::Value::Null,
454            CurvePresentation::Weierstrass { coefficients } => {
455                serde_json::to_value(coefficients).map_err(serialization_error)?
456            }
457        };
458        encode_typed_json(
459            Self::TYPE_ID,
460            [
461                (
462                    "equation",
463                    serde_json::to_value(&self.equation).map_err(serialization_error)?,
464                ),
465                (
466                    "presentation",
467                    serde_json::Value::String(match self.presentation {
468                        CurvePresentation::Plane => "smooth_projective_plane".to_owned(),
469                        CurvePresentation::Weierstrass { .. } => {
470                            "smooth_projective_weierstrass".to_owned()
471                        }
472                    }),
473                ),
474                ("weierstrass_coefficients", coefficients),
475            ],
476        )
477    }
478
479    /// Restore and recheck a serialized smooth projective curve.
480    pub fn from_json(value: &str) -> Result<Self, NoetherError>
481    where
482        R: DeserializeOwned,
483        R::Element: DeserializeOwned,
484    {
485        let mut object = decode_typed_json(
486            value,
487            Self::TYPE_ID,
488            &["equation", "presentation", "weierstrass_coefficients"],
489        )?;
490        let equation: Polynomial<R> = decode_field(&mut object, "equation")?;
491        let presentation: String = decode_field(&mut object, "presentation")?;
492        let coefficients: Option<[R::Element; 5]> =
493            decode_field(&mut object, "weierstrass_coefficients")?;
494        let rebuilt = match (presentation.as_str(), coefficients) {
495            ("smooth_projective_plane", None) => Self::smooth_projective_plane(equation.clone()),
496            ("smooth_projective_weierstrass", Some(coefficients)) => {
497                Self::smooth_projective_weierstrass(
498                    equation.ring().coefficient_ring().clone(),
499                    coefficients,
500                )
501            }
502            _ => Err(CurveError::InvalidWitness(
503                "serialized curve presentation and coefficients agree",
504            )),
505        }
506        .map_err(curve_serialization_error)?;
507        if rebuilt.equation() != &equation {
508            return Err(NoetherError::Serialization(
509                "serialized curve equation is not canonical for its presentation".to_owned(),
510            ));
511        }
512        Ok(rebuilt)
513    }
514}
515
516impl<R: Field> fmt::Debug for Curve<R> {
517    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
518        formatter
519            .debug_struct("Curve")
520            .field("base", &R::TYPE_ID)
521            .field("presentation", &self.presentation_kind())
522            .field("degree", &self.equation.degree(None).ok().flatten())
523            .finish()
524    }
525}
526
527impl<R: Field> fmt::Display for Curve<R> {
528    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
529        write!(
530            formatter,
531            "Curve<{}>({:?})",
532            R::TYPE_ID,
533            self.presentation_kind()
534        )
535    }
536}
537
538impl<R: Field> SemanticType for Curve<R> {
539    const TYPE_ID: &'static str = "noether/commalg/Curve@1";
540
541    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
542        let mut output = self.equation.canonical_bytes()?;
543        output.push(match self.presentation {
544            CurvePresentation::Plane => 0,
545            CurvePresentation::Weierstrass { .. } => 1,
546        });
547        if let CurvePresentation::Weierstrass { coefficients } = &self.presentation {
548            for coefficient in coefficients {
549                append_component(&mut output, &coefficient.canonical_bytes()?)?;
550            }
551        }
552        Ok(output)
553    }
554
555    fn describe() -> &'static TypeDescription {
556        &CURVE_DESCRIPTION
557    }
558}
559
560/// A checked morphism between projective plane curves.
561///
562/// Three equal-degree homogeneous source sections define the target
563/// coordinates. Construction verifies absence of projective base points and
564/// exact satisfaction of the target equation. A nonconstant map between the
565/// admitted smooth projective curves is finite, so closed-point pullback is
566/// computed scheme-theoretically where its inverse image is zero-dimensional.
567#[derive(Clone, PartialEq, Eq)]
568pub struct CurveMorphism<R: Field> {
569    source: Curve<R>,
570    target: Curve<R>,
571    coordinate_images: [Polynomial<R>; 3],
572    coordinate_degree: Integer,
573}
574
575impl<R: Field> CurveMorphism<R> {
576    /// Construct from three equal-degree homogeneous coordinate images.
577    pub fn new(
578        source: Curve<R>,
579        target: Curve<R>,
580        mut coordinate_images: [Polynomial<R>; 3],
581    ) -> Result<Self, CurveError> {
582        if source.base() != target.base() {
583            return Err(ContextMismatchError {
584                expected: source.base().canonical_bytes().map_or_else(
585                    |_| R::TYPE_ID.to_owned(),
586                    |bytes| format!("{}:{} bytes", R::TYPE_ID, bytes.len()),
587                ),
588                actual: "curve morphism target has another base context".to_owned(),
589            }
590            .into());
591        }
592        let ring = source.equation.ring();
593        if coordinate_images
594            .iter()
595            .any(|image| image.ring() != ring || image.is_zero())
596        {
597            return Err(CurveError::InvalidWitness(
598                "curve-morphism coordinates are nonzero sections in the source ring",
599            ));
600        }
601        let degrees = coordinate_images
602            .iter()
603            .map(Polynomial::homogeneous_degree)
604            .collect::<Result<Vec<_>, _>>()?;
605        let coordinate_degree = degrees[0]
606            .clone()
607            .filter(|degree| degree.is_positive())
608            .ok_or(CurveError::InvalidWitness(
609                "curve-morphism coordinates are positive-degree homogeneous sections",
610            ))?;
611        if degrees
612            .iter()
613            .any(|degree| degree.as_ref() != Some(&coordinate_degree))
614        {
615            return Err(CurveError::InvalidWitness(
616                "curve-morphism coordinate sections have one degree",
617            ));
618        }
619        let leading =
620            coordinate_images[0]
621                .leading_coefficient()
622                .ok_or(CurveError::InvalidWitness(
623                    "a curve-morphism coordinate has a leading coefficient",
624                ))?;
625        let normalization = source.base().inverse(&leading).map_err(|_| {
626            CurveError::InvalidWitness("a nonzero coordinate leading coefficient is invertible")
627        })?;
628        coordinate_images = coordinate_images
629            .map(|image| image.scalar_mul(&normalization))
630            .into_iter()
631            .collect::<Result<Vec<_>, _>>()?
632            .try_into()
633            .map_err(|_| CurveError::InvalidWitness("a curve morphism has three coordinates"))?;
634        let coordinate_ideal = ring.ideal(coordinate_images.iter().cloned())?;
635        let base_locus = source.scheme.defining_ideal().sum(&coordinate_ideal)?;
636        if base_locus.dimension()? > 0 {
637            return Err(CurveError::InvalidWitness(
638                "curve-morphism coordinates have no common projective zero on the source curve",
639            ));
640        }
641        let pulled_equation =
642            evaluate_polynomial_at_map(&target.equation, ring, &coordinate_images)?;
643        if !source.scheme.defining_ideal().contains(&pulled_equation)? {
644            return Err(CurveError::InvalidWitness(
645                "curve-morphism coordinates satisfy the target equation on the source",
646            ));
647        }
648        Ok(Self {
649            source,
650            target,
651            coordinate_images,
652            coordinate_degree,
653        })
654    }
655
656    /// Identity morphism.
657    pub fn identity(curve: Curve<R>) -> Result<Self, CurveError> {
658        let ring = curve.equation.ring().clone();
659        Self::new(
660            curve.clone(),
661            curve,
662            [ring.generator(0)?, ring.generator(1)?, ring.generator(2)?],
663        )
664    }
665
666    /// Source curve.
667    pub fn source(&self) -> &Curve<R> {
668        &self.source
669    }
670
671    /// Target curve.
672    pub fn target(&self) -> &Curve<R> {
673        &self.target
674    }
675
676    /// Target homogeneous coordinates as source sections.
677    pub fn coordinate_images(&self) -> &[Polynomial<R>; 3] {
678        &self.coordinate_images
679    }
680
681    /// Common degree of the coordinate sections.
682    pub fn coordinate_degree(&self) -> &Integer {
683        &self.coordinate_degree
684    }
685
686    /// Recheck base-point freeness and target incidence.
687    pub fn verify(&self) -> Result<(), VerificationError> {
688        let rebuilt = Self::new(
689            self.source.clone(),
690            self.target.clone(),
691            self.coordinate_images.clone(),
692        )
693        .map_err(|_| VerificationError::InvalidWitness {
694            claim: "curve morphism is base-point free and lands on its target",
695        })?;
696        if &rebuilt == self {
697            Ok(())
698        } else {
699            Err(VerificationError::InvalidWitness {
700                claim: "curve-morphism coordinate presentation is canonical",
701            })
702        }
703    }
704
705    /// Serialize both endpoint curves and the checked homogeneous sections.
706    pub fn to_json(&self) -> Result<String, NoetherError>
707    where
708        R: Serialize,
709        R::Element: Serialize,
710    {
711        encode_typed_json(
712            Self::TYPE_ID,
713            [
714                ("source", nested_json(self.source.to_json()?)?),
715                ("target", nested_json(self.target.to_json()?)?),
716                (
717                    "coordinate_images",
718                    serde_json::to_value(&self.coordinate_images).map_err(serialization_error)?,
719                ),
720            ],
721        )
722    }
723
724    /// Restore and recheck a serialized curve morphism.
725    pub fn from_json(value: &str) -> Result<Self, NoetherError>
726    where
727        R: DeserializeOwned,
728        R::Element: DeserializeOwned,
729    {
730        let mut object = decode_typed_json(
731            value,
732            Self::TYPE_ID,
733            &["source", "target", "coordinate_images"],
734        )?;
735        let source = Curve::<R>::from_json(&encode_nested_field(&mut object, "source")?)?;
736        let target = Curve::<R>::from_json(&encode_nested_field(&mut object, "target")?)?;
737        let coordinate_images: [Polynomial<R>; 3] = decode_field(&mut object, "coordinate_images")?;
738        Self::new(source, target, coordinate_images).map_err(curve_serialization_error)
739    }
740}
741
742impl<R: Field> fmt::Debug for CurveMorphism<R> {
743    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
744        formatter
745            .debug_struct("CurveMorphism")
746            .field("base", &R::TYPE_ID)
747            .field("coordinate_degree", &self.coordinate_degree)
748            .finish()
749    }
750}
751
752impl<R: Field> fmt::Display for CurveMorphism<R> {
753    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
754        write!(
755            formatter,
756            "CurveMorphism<{}>(coordinate_degree={})",
757            R::TYPE_ID,
758            self.coordinate_degree
759        )
760    }
761}
762
763impl<R: Field> SemanticType for CurveMorphism<R> {
764    const TYPE_ID: &'static str = "noether/commalg/CurveMorphism@1";
765
766    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
767        let mut output = self.source.canonical_bytes()?;
768        append_component(&mut output, &self.target.canonical_bytes()?)?;
769        for image in &self.coordinate_images {
770            append_component(&mut output, &image.canonical_bytes()?)?;
771        }
772        Ok(output)
773    }
774
775    fn describe() -> &'static TypeDescription {
776        &CURVE_MORPHISM_DESCRIPTION
777    }
778}
779
780fn evaluate_polynomial_at_map<R: Field>(
781    polynomial: &Polynomial<R>,
782    target_ring: &PolynomialRing<R>,
783    images: &[Polynomial<R>; 3],
784) -> Result<Polynomial<R>, CurveError> {
785    if polynomial.ring().coefficient_ring() != target_ring.coefficient_ring()
786        || images.iter().any(|image| image.ring() != target_ring)
787    {
788        return Err(ContextMismatchError {
789            expected: target_ring.to_string(),
790            actual: "polynomial map has incompatible coefficient or image contexts".to_owned(),
791        }
792        .into());
793    }
794    let mut result = target_ring.zero();
795    for (exponent, coefficient) in polynomial.terms() {
796        let mut term = target_ring.constant(coefficient.clone())?;
797        for (image, exponent) in images.iter().zip(exponent) {
798            term = (&term * &image.pow_integer(exponent)?)?;
799        }
800        result = (&result + &term)?;
801    }
802    Ok(result)
803}
804
805/// A checked rational point on one exact curve.
806#[derive(Clone, PartialEq, Eq)]
807pub struct CurvePoint<R: Field> {
808    curve: Curve<R>,
809    point: SchemePoint<R>,
810}
811
812impl<R: Field> CurvePoint<R> {
813    /// Owning curve.
814    pub fn curve(&self) -> &Curve<R> {
815        &self.curve
816    }
817
818    /// Canonically normalized projective coordinates.
819    pub fn coordinates(&self) -> &[R::Element] {
820        self.point.coordinates()
821    }
822
823    /// Checked underlying scheme point.
824    pub fn scheme_point(&self) -> &SchemePoint<R> {
825        &self.point
826    }
827
828    /// Embed this rational point as a degree-one closed point.
829    pub fn as_closed_point(&self) -> Result<CurveClosedPoint<R>, CurveError> {
830        let ideal = rational_point_prime_ideal(self)?;
831        let prime = PrimeIdeal::from_verified_parts(ideal, b"rational projective point".to_vec());
832        CurveClosedPoint::from_verified_prime(self.curve.clone(), prime, Some(self.clone()))
833    }
834
835    /// Recheck membership and projective normalization.
836    pub fn verify(&self) -> Result<(), VerificationError> {
837        self.curve.verify()?;
838        self.point
839            .verify()
840            .map_err(|_| VerificationError::InvalidWitness {
841                claim: "curve point satisfies its checked projective scheme",
842            })?;
843        if self.point.scheme() == self.curve.scheme() {
844            Ok(())
845        } else {
846            Err(VerificationError::InvalidWitness {
847                claim: "curve point and curve share one scheme presentation",
848            })
849        }
850    }
851
852    /// Serialize the owning curve and normalized projective coordinates.
853    pub fn to_json(&self) -> Result<String, NoetherError>
854    where
855        R: Serialize,
856        R::Element: Serialize,
857    {
858        encode_typed_json(
859            Self::TYPE_ID,
860            [
861                ("curve", nested_json(self.curve.to_json()?)?),
862                (
863                    "coordinates",
864                    serde_json::to_value(self.coordinates()).map_err(serialization_error)?,
865                ),
866            ],
867        )
868    }
869
870    /// Restore and recheck a normalized rational point.
871    pub fn from_json(value: &str) -> Result<Self, NoetherError>
872    where
873        R: DeserializeOwned,
874        R::Element: DeserializeOwned,
875    {
876        let mut object = decode_typed_json(value, Self::TYPE_ID, &["curve", "coordinates"])?;
877        let curve = Curve::<R>::from_json(&encode_nested_field(&mut object, "curve")?)?;
878        let coordinates: Vec<R::Element> = decode_field(&mut object, "coordinates")?;
879        curve.point(coordinates).map_err(curve_serialization_error)
880    }
881}
882
883fn rational_point_prime_ideal<R: Field>(
884    point: &CurvePoint<R>,
885) -> Result<super::Ideal<PolynomialRing<R>>, CurveError> {
886    let ring = point.curve.equation.ring();
887    let zero = point.curve.base().zero();
888    let pivot = point
889        .coordinates()
890        .iter()
891        .position(|coordinate| coordinate != &zero)
892        .ok_or(CurveError::InvalidWitness(
893            "a checked projective point has a nonzero coordinate",
894        ))?;
895    let pivot_generator = ring.generator(pivot)?;
896    let mut generators = Vec::with_capacity(2);
897    for coordinate in 0..3 {
898        if coordinate == pivot {
899            continue;
900        }
901        let scaled = pivot_generator.scalar_mul(&point.coordinates()[coordinate])?;
902        generators.push((&ring.generator(coordinate)? - &scaled)?);
903    }
904    ring.ideal(generators).map_err(Into::into)
905}
906
907impl<R: Field> fmt::Debug for CurvePoint<R> {
908    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
909        formatter
910            .debug_struct("CurvePoint")
911            .field("base", &R::TYPE_ID)
912            .field("coordinates", &self.coordinates().len())
913            .finish()
914    }
915}
916
917impl<R: Field> fmt::Display for CurvePoint<R> {
918    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
919        write!(formatter, "CurvePoint<{}>", R::TYPE_ID)
920    }
921}
922
923impl<R: Field> SemanticType for CurvePoint<R> {
924    const TYPE_ID: &'static str = "noether/commalg/CurvePoint@1";
925
926    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
927        let mut output = self.curve.canonical_bytes()?;
928        for coordinate in self.coordinates() {
929            append_component(&mut output, &coordinate.canonical_bytes()?)?;
930        }
931        Ok(output)
932    }
933
934    fn describe() -> &'static TypeDescription {
935        &CURVE_POINT_DESCRIPTION
936    }
937}
938
939/// A closed point (place) on a checked projective curve.
940///
941/// The point is represented by its homogeneous prime ideal. Its degree is the
942/// exact standard-graded degree of that ideal. Degree-one points optionally
943/// retain their checked rational coordinates for local evaluation.
944#[derive(Clone)]
945pub struct CurveClosedPoint<R: Field> {
946    curve: Curve<R>,
947    prime_ideal: PrimeIdeal<PolynomialRing<R>>,
948    degree: Integer,
949    rational_point: Option<CurvePoint<R>>,
950}
951
952impl<R: Field> CurveClosedPoint<R> {
953    /// Construct from a previously certified homogeneous prime ideal.
954    pub fn from_prime_ideal(
955        curve: Curve<R>,
956        prime_ideal: PrimeIdeal<PolynomialRing<R>>,
957    ) -> Result<Self, CurveError> {
958        Self::from_verified_prime(curve, prime_ideal, None)
959    }
960
961    fn from_verified_prime(
962        curve: Curve<R>,
963        prime_ideal: PrimeIdeal<PolynomialRing<R>>,
964        mut rational_point: Option<CurvePoint<R>>,
965    ) -> Result<Self, CurveError> {
966        let ideal = prime_ideal.ideal();
967        if ideal.ring() != curve.equation.ring() {
968            return Err(ContextMismatchError {
969                expected: curve.equation.ring().to_string(),
970                actual: ideal.ring().to_string(),
971            }
972            .into());
973        }
974        if ideal.generators().iter().any(|generator| {
975            generator
976                .homogeneous_degree()
977                .map_or(true, |degree| degree.is_none() && !generator.is_zero())
978        }) {
979            return Err(CurveError::InvalidWitness(
980                "a projective closed-point prime ideal is homogeneous",
981            ));
982        }
983        if ideal.dimension()? != 1 || !ideal.contains(&curve.equation)? {
984            return Err(CurveError::InvalidWitness(
985                "the homogeneous prime ideal cuts out a closed point on the curve",
986            ));
987        }
988        let degree = ideal.degree()?;
989        if !degree.is_positive() {
990            return Err(CurveError::InvalidWitness(
991                "a curve closed point has positive degree",
992            ));
993        }
994        if rational_point.is_none() && degree == Integer::one() {
995            rational_point = Some(recover_degree_one_point(&curve, ideal)?);
996        }
997        if let Some(point) = &rational_point {
998            point.verify().map_err(|_| {
999                CurveError::InvalidWitness("retained rational coordinates verify on the curve")
1000            })?;
1001            if point.curve() != &curve || degree != Integer::one() {
1002                return Err(CurveError::InvalidWitness(
1003                    "retained rational coordinates describe this degree-one point",
1004                ));
1005            }
1006            if rational_point_prime_ideal(point)? != *ideal {
1007                return Err(CurveError::InvalidWitness(
1008                    "retained rational coordinates define the supplied degree-one prime",
1009                ));
1010            }
1011        }
1012        Ok(Self {
1013            curve,
1014            prime_ideal,
1015            degree,
1016            rational_point,
1017        })
1018    }
1019
1020    /// Owning curve.
1021    pub fn curve(&self) -> &Curve<R> {
1022        &self.curve
1023    }
1024
1025    /// Homogeneous prime ideal defining the point.
1026    pub fn prime_ideal(&self) -> &PrimeIdeal<PolynomialRing<R>> {
1027        &self.prime_ideal
1028    }
1029
1030    /// Residue degree over the constant field.
1031    pub fn degree(&self) -> &Integer {
1032        &self.degree
1033    }
1034
1035    /// Retained rational coordinates, when this point was embedded from them.
1036    pub fn rational_point(&self) -> Option<&CurvePoint<R>> {
1037        self.rational_point.as_ref()
1038    }
1039
1040    /// Recheck incidence, dimension, degree, and any rational coordinates.
1041    pub fn verify(&self) -> Result<(), VerificationError> {
1042        let rebuilt = Self::from_verified_prime(
1043            self.curve.clone(),
1044            self.prime_ideal.clone(),
1045            self.rational_point.clone(),
1046        )
1047        .map_err(|_| VerificationError::InvalidWitness {
1048            claim: "closed point is a homogeneous prime of projective dimension zero on its curve",
1049        })?;
1050        if &rebuilt == self {
1051            Ok(())
1052        } else {
1053            Err(VerificationError::InvalidWitness {
1054                claim: "closed-point degree and presentation recompute canonically",
1055            })
1056        }
1057    }
1058}
1059
1060fn recover_degree_one_point<R: Field>(
1061    curve: &Curve<R>,
1062    ideal: &super::Ideal<PolynomialRing<R>>,
1063) -> Result<CurvePoint<R>, CurveError> {
1064    let projective_ring = curve.equation.ring();
1065    let chart = (0..3)
1066        .find_map(|coordinate| {
1067            projective_ring
1068                .generator(coordinate)
1069                .ok()
1070                .and_then(|generator| {
1071                    ideal
1072                        .contains(&generator)
1073                        .ok()
1074                        .map(|inside| (inside, coordinate))
1075                })
1076                .and_then(|(inside, coordinate)| (!inside).then_some(coordinate))
1077        })
1078        .ok_or(CurveError::InvalidWitness(
1079            "a projective degree-one prime meets an affine chart",
1080        ))?;
1081    let affine_ring = PolynomialRing::new(
1082        curve.base().clone(),
1083        projective_ring
1084            .variables()
1085            .iter()
1086            .enumerate()
1087            .filter(|(coordinate, _)| *coordinate != chart)
1088            .map(|(_, name)| name.clone()),
1089        MonomialOrder::GradedReverseLexicographic,
1090    )?;
1091    let affine_ideal = affine_ring.ideal(
1092        ideal
1093            .generators()
1094            .iter()
1095            .map(|generator| dehomogenize_to_chart(generator, &affine_ring, chart))
1096            .collect::<Result<Vec<_>, _>>()?,
1097    )?;
1098    if affine_ideal.dimension()? != 0 || affine_ideal.degree()? != Integer::one() {
1099        return Err(CurveError::InvalidWitness(
1100            "a degree-one projective prime has a degree-one affine chart",
1101        ));
1102    }
1103    let mut coordinates = vec![curve.base().zero(); 3];
1104    coordinates[chart] = curve.base().one();
1105    let mut affine_coordinate = 0;
1106    for (projective_coordinate, coordinate_value) in coordinates.iter_mut().enumerate() {
1107        if projective_coordinate == chart {
1108            continue;
1109        }
1110        let generator = affine_ring.generator(affine_coordinate)?;
1111        let normal = affine_ideal.normal_form(&generator)?;
1112        let constant = normal.constant_coefficient();
1113        if normal != affine_ring.constant(constant.clone())? {
1114            return Err(CurveError::InvalidWitness(
1115                "a degree-one affine residue class is constant",
1116            ));
1117        }
1118        *coordinate_value = constant;
1119        affine_coordinate += 1;
1120    }
1121    let point = curve.point(coordinates)?;
1122    if rational_point_prime_ideal(&point)? != *ideal {
1123        return Err(CurveError::InvalidWitness(
1124            "recovered degree-one coordinates define the supplied prime",
1125        ));
1126    }
1127    Ok(point)
1128}
1129
1130impl<R: Field> PartialEq for CurveClosedPoint<R> {
1131    fn eq(&self, other: &Self) -> bool {
1132        self.curve == other.curve && self.prime_ideal == other.prime_ideal
1133    }
1134}
1135
1136impl<R: Field> Eq for CurveClosedPoint<R> {}
1137
1138impl<R: Field> fmt::Debug for CurveClosedPoint<R> {
1139    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1140        formatter
1141            .debug_struct("CurveClosedPoint")
1142            .field("base", &R::TYPE_ID)
1143            .field("degree", &self.degree)
1144            .field("rational", &self.rational_point.is_some())
1145            .finish()
1146    }
1147}
1148
1149impl<R: Field> fmt::Display for CurveClosedPoint<R> {
1150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1151        write!(
1152            formatter,
1153            "CurveClosedPoint<{}>(degree={})",
1154            R::TYPE_ID,
1155            self.degree
1156        )
1157    }
1158}
1159
1160impl<R: Field> SemanticType for CurveClosedPoint<R> {
1161    const TYPE_ID: &'static str = "noether/commalg/CurveClosedPoint@1";
1162
1163    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1164        let mut output = self.curve.canonical_bytes()?;
1165        append_component(&mut output, &self.prime_ideal.canonical_bytes()?)?;
1166        Ok(output)
1167    }
1168
1169    fn describe() -> &'static TypeDescription {
1170        &CURVE_CLOSED_POINT_DESCRIPTION
1171    }
1172}
1173
1174fn closed_point_to_json<R>(point: &CurveClosedPoint<R>) -> Result<String, NoetherError>
1175where
1176    R: Field + Serialize,
1177    R::Element: Serialize,
1178{
1179    encode_typed_json(
1180        CurveClosedPoint::<R>::TYPE_ID,
1181        [
1182            ("curve", nested_json(point.curve.to_json()?)?),
1183            (
1184                "defining_ideal",
1185                serde_json::to_value(point.prime_ideal.ideal()).map_err(serialization_error)?,
1186            ),
1187        ],
1188    )
1189}
1190
1191fn decode_closed_point_parts<R>(
1192    value: &str,
1193) -> Result<(Curve<R>, super::Ideal<PolynomialRing<R>>), NoetherError>
1194where
1195    R: Field + DeserializeOwned,
1196    R::Element: DeserializeOwned,
1197{
1198    let mut object = decode_typed_json(
1199        value,
1200        CurveClosedPoint::<R>::TYPE_ID,
1201        &["curve", "defining_ideal"],
1202    )?;
1203    let curve = Curve::<R>::from_json(&encode_nested_field(&mut object, "curve")?)?;
1204    let ideal = decode_field(&mut object, "defining_ideal")?;
1205    Ok((curve, ideal))
1206}
1207
1208fn closed_point_from_degree_one_json<R>(value: &str) -> Result<CurveClosedPoint<R>, NoetherError>
1209where
1210    R: Field + DeserializeOwned,
1211    R::Element: DeserializeOwned,
1212{
1213    let (curve, ideal) = decode_closed_point_parts(value)?;
1214    if ideal
1215        .degree()
1216        .map_err(|error| curve_serialization_error(error.into()))?
1217        != Integer::one()
1218    {
1219        return Err(NoetherError::Serialization(
1220            "closed points over this coefficient context deserialize only in degree one".to_owned(),
1221        ));
1222    }
1223    CurveClosedPoint::from_verified_prime(
1224        curve,
1225        PrimeIdeal::from_verified_parts(ideal, b"recovered rational projective point".to_vec()),
1226        None,
1227    )
1228    .map_err(curve_serialization_error)
1229}
1230
1231fn closed_point_from_factorized_json<R, F>(
1232    value: &str,
1233    factor: F,
1234) -> Result<CurveClosedPoint<R>, NoetherError>
1235where
1236    R: Field + DeserializeOwned,
1237    R::Element: DeserializeOwned,
1238    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>,
1239{
1240    let (curve, ideal) = decode_closed_point_parts(value)?;
1241    if ideal
1242        .degree()
1243        .map_err(|error| curve_serialization_error(error.into()))?
1244        == Integer::one()
1245    {
1246        return CurveClosedPoint::from_verified_prime(
1247            curve,
1248            PrimeIdeal::from_verified_parts(ideal, b"recovered rational projective point".to_vec()),
1249            None,
1250        )
1251        .map_err(curve_serialization_error);
1252    }
1253    certify_closed_point_prime(curve, ideal, factor).map_err(curve_serialization_error)
1254}
1255
1256fn certify_closed_point_prime<R, F>(
1257    curve: Curve<R>,
1258    ideal: super::Ideal<PolynomialRing<R>>,
1259    factor: F,
1260) -> Result<CurveClosedPoint<R>, CurveError>
1261where
1262    R: Field,
1263    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>,
1264{
1265    let projective_ring = curve.equation.ring();
1266    let chart = (0..3)
1267        .find_map(|coordinate| {
1268            let generator = projective_ring.generator(coordinate).ok()?;
1269            (!ideal.contains(&generator).ok()?).then_some(coordinate)
1270        })
1271        .ok_or(CurveError::InvalidWitness(
1272            "a projective closed point meets an affine chart",
1273        ))?;
1274    let affine_ring = PolynomialRing::new(
1275        curve.base().clone(),
1276        projective_ring
1277            .variables()
1278            .iter()
1279            .enumerate()
1280            .filter(|(coordinate, _)| *coordinate != chart)
1281            .map(|(_, name)| name.clone()),
1282        MonomialOrder::GradedReverseLexicographic,
1283    )?;
1284    let affine_ideal = affine_ring.ideal(
1285        ideal
1286            .generators()
1287            .iter()
1288            .map(|generator| dehomogenize_to_chart(generator, &affine_ring, chart))
1289            .collect::<Result<Vec<_>, _>>()?,
1290    )?;
1291    if affine_ideal.dimension()? != 0 {
1292        return Err(CurveError::InvalidWitness(
1293            "a projective closed point has a zero-dimensional affine chart",
1294        ));
1295    }
1296    let representation = affine_ideal.rational_univariate_representation()?;
1297    representation
1298        .verify()
1299        .map_err(|_| CurveError::InvalidWitness("the affine point RUR verifies"))?;
1300    if !representation.is_reduced() {
1301        return Err(CurveError::InvalidWitness(
1302            "a closed-point residue algebra is reduced",
1303        ));
1304    }
1305    let factorization = factor(representation.equation())?;
1306    factorization.verify()?;
1307    let [(irreducible, multiplicity)] = factorization.factors() else {
1308        return Err(CurveError::InvalidWitness(
1309            "a closed-point residue algebra has one irreducible parameter factor",
1310        ));
1311    };
1312    if *multiplicity != 1 {
1313        return Err(CurveError::InvalidWitness(
1314            "a closed-point parameter factor has multiplicity one",
1315        ));
1316    }
1317    let factor_degree = irreducible
1318        .degree(Some(0))?
1319        .ok_or(CurveError::InvalidWitness(
1320            "a closed-point parameter factor is nonconstant",
1321        ))?;
1322    if integer_to_usize(&factor_degree)? != representation.quotient_dimension()
1323        || ideal.degree()? != factor_degree
1324    {
1325        return Err(CurveError::InvalidWitness(
1326            "the irreducible affine residue field accounts for the complete projective degree",
1327        ));
1328    }
1329    CurveClosedPoint::from_verified_prime(
1330        curve,
1331        PrimeIdeal::from_verified_parts(
1332            ideal,
1333            b"verified irreducible affine RUR residue field".to_vec(),
1334        ),
1335        None,
1336    )
1337}
1338
1339macro_rules! impl_factorized_closed_point_json {
1340    ($field:ty) => {
1341        impl CurveClosedPoint<$field> {
1342            /// Serialize this place by its checked curve and homogeneous prime ideal.
1343            pub fn to_json(&self) -> Result<String, NoetherError> {
1344                closed_point_to_json(self)
1345            }
1346
1347            /// Restore and prove maximality in a deterministic affine chart.
1348            pub fn from_json(value: &str) -> Result<Self, NoetherError> {
1349                closed_point_from_factorized_json(value, |polynomial: &Polynomial<$field>| {
1350                    polynomial.factor()
1351                })
1352            }
1353        }
1354    };
1355}
1356
1357impl_factorized_closed_point_json!(RationalField);
1358impl_factorized_closed_point_json!(FiniteField);
1359
1360macro_rules! impl_degree_one_closed_point_json {
1361    ($field:ty) => {
1362        impl CurveClosedPoint<$field> {
1363            /// Serialize a degree-one place over this exact function field.
1364            pub fn to_json(&self) -> Result<String, NoetherError> {
1365                if self.degree() != &Integer::one() {
1366                    return Err(NoetherError::Serialization(
1367                        "non-rational closed-point serialization requires coefficient-field factorization"
1368                            .to_owned(),
1369                    ));
1370                }
1371                closed_point_to_json(self)
1372            }
1373
1374            /// Restore and prove a degree-one place from its rational coordinates.
1375            pub fn from_json(value: &str) -> Result<Self, NoetherError> {
1376                closed_point_from_degree_one_json(value)
1377            }
1378        }
1379    };
1380}
1381
1382impl_degree_one_closed_point_json!(RationalFunctionField<RationalField>);
1383impl_degree_one_closed_point_json!(RationalFunctionField<FiniteField>);
1384impl_degree_one_closed_point_json!(NumberField);
1385
1386/// A canonical Weil divisor with arbitrary-precision integral coefficients.
1387#[derive(Clone, PartialEq, Eq)]
1388pub struct WeilDivisor<R: Field> {
1389    curve: Curve<R>,
1390    terms: Vec<(CurveClosedPoint<R>, Integer)>,
1391}
1392
1393impl<R: Field> WeilDivisor<R> {
1394    /// Construct and canonically combine a finite family of point coefficients.
1395    pub fn new<I>(curve: Curve<R>, terms: I) -> Result<Self, CurveError>
1396    where
1397        I: IntoIterator<Item = (CurveClosedPoint<R>, Integer)>,
1398    {
1399        let mut combined: Vec<(CurveClosedPoint<R>, Integer)> = Vec::new();
1400        for (point, coefficient) in terms {
1401            if point.curve() != &curve {
1402                return Err(ContextMismatchError {
1403                    expected: Curve::<R>::TYPE_ID.to_owned(),
1404                    actual: "divisor point lies on another curve".to_owned(),
1405                }
1406                .into());
1407            }
1408            if coefficient.is_zero() {
1409                continue;
1410            }
1411            if let Some((_, existing)) = combined.iter_mut().find(|(other, _)| other == &point) {
1412                *existing = &*existing + &coefficient;
1413            } else {
1414                combined.push((point, coefficient));
1415            }
1416        }
1417        combined.retain(|(_, coefficient)| !coefficient.is_zero());
1418        let mut keyed = combined
1419            .into_iter()
1420            .map(|term| -> Result<_, CurveError> {
1421                let key = term.0.canonical_bytes().map_err(|_| {
1422                    CurveError::InvalidWitness("closed-point identity is serializable")
1423                })?;
1424                Ok((key, term))
1425            })
1426            .collect::<Result<Vec<_>, _>>()?;
1427        keyed.sort_by(|(left, _), (right, _)| left.cmp(right));
1428        let combined = keyed.into_iter().map(|(_, term)| term).collect();
1429        Ok(Self {
1430            curve,
1431            terms: combined,
1432        })
1433    }
1434
1435    /// Zero divisor on one curve.
1436    pub fn zero(curve: Curve<R>) -> Self {
1437        Self {
1438            curve,
1439            terms: Vec::new(),
1440        }
1441    }
1442
1443    /// Owning curve.
1444    pub fn curve(&self) -> &Curve<R> {
1445        &self.curve
1446    }
1447
1448    /// Deterministically ordered nonzero point coefficients.
1449    pub fn terms(&self) -> &[(CurveClosedPoint<R>, Integer)] {
1450        &self.terms
1451    }
1452
1453    /// Closed support in deterministic order.
1454    pub fn support(&self) -> impl Iterator<Item = &CurveClosedPoint<R>> {
1455        self.terms.iter().map(|(point, _)| point)
1456    }
1457
1458    /// Coefficient of one closed point.
1459    pub fn coefficient(&self, point: &CurveClosedPoint<R>) -> Result<Integer, CurveError> {
1460        if point.curve() != &self.curve {
1461            return Err(ContextMismatchError {
1462                expected: Curve::<R>::TYPE_ID.to_owned(),
1463                actual: "divisor coefficient requested at a point on another curve".to_owned(),
1464            }
1465            .into());
1466        }
1467        Ok(self
1468            .terms
1469            .iter()
1470            .find_map(|(candidate, coefficient)| (candidate == point).then(|| coefficient.clone()))
1471            .unwrap_or_else(Integer::zero))
1472    }
1473
1474    /// Exact divisor degree `sum n_P [k(P):K]`.
1475    pub fn degree(&self) -> Integer {
1476        self.terms
1477            .iter()
1478            .fold(Integer::zero(), |degree, (point, coefficient)| {
1479                degree + coefficient * point.degree()
1480            })
1481    }
1482
1483    /// Effective positive part.
1484    pub fn positive_part(&self) -> Self {
1485        Self {
1486            curve: self.curve.clone(),
1487            terms: self
1488                .terms
1489                .iter()
1490                .filter(|(_, coefficient)| coefficient.is_positive())
1491                .cloned()
1492                .collect(),
1493        }
1494    }
1495
1496    /// Effective negative part, with positive coefficients, so `D=D+ - D-`.
1497    pub fn negative_part(&self) -> Self {
1498        Self {
1499            curve: self.curve.clone(),
1500            terms: self
1501                .terms
1502                .iter()
1503                .filter(|(_, coefficient)| coefficient.is_negative())
1504                .map(|(point, coefficient)| (point.clone(), -coefficient))
1505                .collect(),
1506        }
1507    }
1508
1509    /// Scale every coefficient by an arbitrary-precision integer.
1510    pub fn scale(&self, scalar: &Integer) -> Self {
1511        Self {
1512            curve: self.curve.clone(),
1513            terms: self
1514                .terms
1515                .iter()
1516                .filter_map(|(point, coefficient)| {
1517                    let coefficient = coefficient * scalar;
1518                    (!coefficient.is_zero()).then(|| (point.clone(), coefficient))
1519                })
1520                .collect(),
1521        }
1522    }
1523
1524    /// Add another divisor on the same curve.
1525    pub fn checked_add(&self, other: &Self) -> Result<Self, CurveError> {
1526        if self.curve != other.curve {
1527            return Err(ContextMismatchError {
1528                expected: Curve::<R>::TYPE_ID.to_owned(),
1529                actual: "divisor belongs to another curve".to_owned(),
1530            }
1531            .into());
1532        }
1533        Self::new(
1534            self.curve.clone(),
1535            self.terms
1536                .iter()
1537                .cloned()
1538                .chain(other.terms.iter().cloned()),
1539        )
1540    }
1541
1542    /// Subtract another divisor on the same curve.
1543    pub fn checked_sub(&self, other: &Self) -> Result<Self, CurveError> {
1544        self.checked_add(&other.scale(&Integer::from(-1)))
1545    }
1546
1547    /// Negate all coefficients.
1548    pub fn negated(&self) -> Self {
1549        self.scale(&Integer::from(-1))
1550    }
1551
1552    /// Compute the exact Riemann--Roch space `L(D)` on this smooth plane curve.
1553    pub fn riemann_roch_space(&self) -> Result<RiemannRochSpace<R>, CurveError> {
1554        compute_riemann_roch_space(self)
1555    }
1556
1557    /// Recheck every point, coefficient normalization, ordering, and degree data.
1558    pub fn verify(&self) -> Result<(), VerificationError> {
1559        for (point, coefficient) in &self.terms {
1560            point.verify()?;
1561            if point.curve() != &self.curve || coefficient.is_zero() {
1562                return Err(VerificationError::InvalidWitness {
1563                    claim: "divisor support belongs to one curve with nonzero coefficients",
1564                });
1565            }
1566        }
1567        let rebuilt = Self::new(self.curve.clone(), self.terms.clone()).map_err(|_| {
1568            VerificationError::InvalidWitness {
1569                claim: "divisor terms canonically combine",
1570            }
1571        })?;
1572        if &rebuilt == self {
1573            Ok(())
1574        } else {
1575            Err(VerificationError::InvalidWitness {
1576                claim: "divisor support is deterministically ordered and combined",
1577            })
1578        }
1579    }
1580}
1581
1582impl<R: Field> fmt::Debug for WeilDivisor<R> {
1583    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1584        formatter
1585            .debug_struct("WeilDivisor")
1586            .field("base", &R::TYPE_ID)
1587            .field("support", &self.terms.len())
1588            .field("degree", &self.degree())
1589            .finish()
1590    }
1591}
1592
1593impl<R: Field> fmt::Display for WeilDivisor<R> {
1594    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1595        write!(
1596            formatter,
1597            "WeilDivisor<{}>(support={}, degree={})",
1598            R::TYPE_ID,
1599            self.terms.len(),
1600            self.degree()
1601        )
1602    }
1603}
1604
1605impl<R: Field> SemanticType for WeilDivisor<R> {
1606    const TYPE_ID: &'static str = "noether/commalg/WeilDivisor@1";
1607
1608    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1609        let mut output = self.curve.canonical_bytes()?;
1610        for (point, coefficient) in &self.terms {
1611            append_component(&mut output, &point.canonical_bytes()?)?;
1612            append_component(&mut output, &coefficient.canonical_bytes()?)?;
1613        }
1614        Ok(output)
1615    }
1616
1617    fn describe() -> &'static TypeDescription {
1618        &WEIL_DIVISOR_DESCRIPTION
1619    }
1620}
1621
1622fn divisor_to_json<R>(divisor: &WeilDivisor<R>) -> Result<String, NoetherError>
1623where
1624    R: Field + Serialize,
1625    R::Element: Serialize,
1626    CurveClosedPoint<R>: ClosedPointJson,
1627{
1628    let terms = divisor
1629        .terms
1630        .iter()
1631        .map(|(point, coefficient)| {
1632            Ok(serde_json::Value::Array(vec![
1633                nested_json(point.closed_point_to_json()?)?,
1634                serde_json::to_value(coefficient).map_err(serialization_error)?,
1635            ]))
1636        })
1637        .collect::<Result<Vec<_>, NoetherError>>()?;
1638    encode_typed_json(
1639        WeilDivisor::<R>::TYPE_ID,
1640        [
1641            ("curve", nested_json(divisor.curve.to_json()?)?),
1642            ("terms", serde_json::Value::Array(terms)),
1643        ],
1644    )
1645}
1646
1647fn divisor_from_json<R>(value: &str) -> Result<WeilDivisor<R>, NoetherError>
1648where
1649    R: Field + Serialize + DeserializeOwned,
1650    R::Element: Serialize + DeserializeOwned,
1651    CurveClosedPoint<R>: ClosedPointJson,
1652{
1653    let original: serde_json::Value = serde_json::from_str(value).map_err(serialization_error)?;
1654    let mut object = decode_typed_json(value, WeilDivisor::<R>::TYPE_ID, &["curve", "terms"])?;
1655    let curve = Curve::<R>::from_json(&encode_nested_field(&mut object, "curve")?)?;
1656    let terms = object
1657        .remove("terms")
1658        .and_then(|value| value.as_array().cloned())
1659        .ok_or_else(|| {
1660            NoetherError::Serialization("serialized divisor terms must be an array".to_owned())
1661        })?;
1662    let terms = terms
1663        .into_iter()
1664        .map(|term| {
1665            let [point, coefficient]: [serde_json::Value; 2] = term
1666                .as_array()
1667                .cloned()
1668                .and_then(|values| values.try_into().ok())
1669                .ok_or_else(|| {
1670                    NoetherError::Serialization(
1671                        "each serialized divisor term must be a point-coefficient pair".to_owned(),
1672                    )
1673                })?;
1674            let point = CurveClosedPoint::<R>::closed_point_from_json(
1675                &serde_json::to_string(&point).map_err(serialization_error)?,
1676            )?;
1677            let coefficient = serde_json::from_value(coefficient).map_err(serialization_error)?;
1678            Ok((point, coefficient))
1679        })
1680        .collect::<Result<Vec<_>, NoetherError>>()?;
1681    let rebuilt = WeilDivisor::new(curve, terms).map_err(curve_serialization_error)?;
1682    let canonical: serde_json::Value =
1683        serde_json::from_str(&divisor_to_json(&rebuilt)?).map_err(serialization_error)?;
1684    if canonical != original {
1685        return Err(NoetherError::Serialization(
1686            "serialized divisor support is not canonically ordered and combined".to_owned(),
1687        ));
1688    }
1689    Ok(rebuilt)
1690}
1691
1692trait ClosedPointJson: Sized {
1693    fn closed_point_to_json(&self) -> Result<String, NoetherError>;
1694    fn closed_point_from_json(value: &str) -> Result<Self, NoetherError>;
1695}
1696
1697macro_rules! impl_curve_divisor_json {
1698    ($field:ty) => {
1699        impl ClosedPointJson for CurveClosedPoint<$field> {
1700            fn closed_point_to_json(&self) -> Result<String, NoetherError> {
1701                Self::to_json(self)
1702            }
1703
1704            fn closed_point_from_json(value: &str) -> Result<Self, NoetherError> {
1705                Self::from_json(value)
1706            }
1707        }
1708
1709        impl WeilDivisor<$field> {
1710            /// Serialize this canonically ordered finite divisor.
1711            pub fn to_json(&self) -> Result<String, NoetherError> {
1712                divisor_to_json(self)
1713            }
1714
1715            /// Restore every place, combine support, and reject noncanonical input.
1716            pub fn from_json(value: &str) -> Result<Self, NoetherError> {
1717                divisor_from_json(value)
1718            }
1719        }
1720    };
1721}
1722
1723impl_curve_divisor_json!(RationalField);
1724impl_curve_divisor_json!(FiniteField);
1725impl_curve_divisor_json!(RationalFunctionField<RationalField>);
1726impl_curve_divisor_json!(RationalFunctionField<FiniteField>);
1727impl_curve_divisor_json!(NumberField);
1728
1729impl<R: Field> Add<&WeilDivisor<R>> for &WeilDivisor<R> {
1730    type Output = Result<WeilDivisor<R>, CurveError>;
1731
1732    fn add(self, rhs: &WeilDivisor<R>) -> Self::Output {
1733        self.checked_add(rhs)
1734    }
1735}
1736
1737impl<R: Field> Sub<&WeilDivisor<R>> for &WeilDivisor<R> {
1738    type Output = Result<WeilDivisor<R>, CurveError>;
1739
1740    fn sub(self, rhs: &WeilDivisor<R>) -> Self::Output {
1741        self.checked_sub(rhs)
1742    }
1743}
1744
1745impl<R: Field> Neg for &WeilDivisor<R> {
1746    type Output = WeilDivisor<R>;
1747
1748    fn neg(self) -> Self::Output {
1749        self.negated()
1750    }
1751}
1752
1753/// A verified finite-dimensional Riemann--Roch space over the constant field.
1754///
1755/// The deterministic basis consists directly of curve functions. Private
1756/// equal-degree numerator/denominator sections retain the exact ideal-theoretic
1757/// construction witness used by [`verify`](Self::verify).
1758#[derive(Clone, PartialEq, Eq)]
1759pub struct RiemannRochSpace<R: Field> {
1760    divisor: WeilDivisor<R>,
1761    basis: Vec<CurveFunction<R>>,
1762    denominator_section: Polynomial<R>,
1763    numerator_sections: Vec<Polynomial<R>>,
1764}
1765
1766impl<R: Field> RiemannRochSpace<R> {
1767    /// Source divisor.
1768    pub fn divisor(&self) -> &WeilDivisor<R> {
1769        &self.divisor
1770    }
1771
1772    /// Dimension over the constant field.
1773    pub fn dimension(&self) -> usize {
1774        self.basis.len()
1775    }
1776
1777    /// Deterministically ordered basis in the curve function field.
1778    pub fn basis(&self) -> &[CurveFunction<R>] {
1779        &self.basis
1780    }
1781
1782    /// Recompute the complete ideal-theoretic section calculation.
1783    pub fn verify(&self) -> Result<(), VerificationError> {
1784        self.divisor.verify()?;
1785        for function in &self.basis {
1786            function.verify()?;
1787            if function.field().curve() != self.divisor.curve() {
1788                return Err(VerificationError::InvalidWitness {
1789                    claim: "Riemann--Roch basis functions belong to the divisor's curve",
1790                });
1791            }
1792        }
1793        let rebuilt = compute_riemann_roch_space(&self.divisor).map_err(|_| {
1794            VerificationError::InvalidWitness {
1795                claim: "Riemann--Roch ideal quotient and section-space calculation recomputes",
1796            }
1797        })?;
1798        if &rebuilt == self {
1799            Ok(())
1800        } else {
1801            Err(VerificationError::InvalidWitness {
1802                claim: "Riemann--Roch basis and dimension are canonical",
1803            })
1804        }
1805    }
1806}
1807
1808impl<R: Field> fmt::Debug for RiemannRochSpace<R> {
1809    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1810        formatter
1811            .debug_struct("RiemannRochSpace")
1812            .field("base", &R::TYPE_ID)
1813            .field("dimension", &self.dimension())
1814            .field("divisor_degree", &self.divisor.degree())
1815            .finish()
1816    }
1817}
1818
1819impl<R: Field> fmt::Display for RiemannRochSpace<R> {
1820    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1821        write!(
1822            formatter,
1823            "RiemannRochSpace<{}>(dimension={}, divisor_degree={})",
1824            R::TYPE_ID,
1825            self.dimension(),
1826            self.divisor.degree()
1827        )
1828    }
1829}
1830
1831impl<R: Field> SemanticType for RiemannRochSpace<R> {
1832    const TYPE_ID: &'static str = "noether/commalg/RiemannRochSpace@1";
1833
1834    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
1835        let mut output = self.divisor.canonical_bytes()?;
1836        append_component(&mut output, &self.denominator_section.canonical_bytes()?)?;
1837        for numerator in &self.numerator_sections {
1838            append_component(&mut output, &numerator.canonical_bytes()?)?;
1839        }
1840        Ok(output)
1841    }
1842
1843    fn describe() -> &'static TypeDescription {
1844        &RIEMANN_ROCH_SPACE_DESCRIPTION
1845    }
1846}
1847
1848fn riemann_roch_to_json<R>(space: &RiemannRochSpace<R>) -> Result<String, NoetherError>
1849where
1850    R: Field + Serialize,
1851    R::Element: Serialize,
1852    WeilDivisor<R>: DivisorJson,
1853{
1854    encode_typed_json(
1855        RiemannRochSpace::<R>::TYPE_ID,
1856        [("divisor", nested_json(space.divisor.divisor_to_json()?)?)],
1857    )
1858}
1859
1860fn riemann_roch_from_json<R>(value: &str) -> Result<RiemannRochSpace<R>, NoetherError>
1861where
1862    R: Field,
1863    WeilDivisor<R>: DivisorJson,
1864{
1865    let mut object = decode_typed_json(value, RiemannRochSpace::<R>::TYPE_ID, &["divisor"])?;
1866    let divisor =
1867        WeilDivisor::<R>::divisor_from_json(&encode_nested_field(&mut object, "divisor")?)?;
1868    divisor
1869        .riemann_roch_space()
1870        .map_err(curve_serialization_error)
1871}
1872
1873trait DivisorJson: Sized {
1874    fn divisor_to_json(&self) -> Result<String, NoetherError>;
1875    fn divisor_from_json(value: &str) -> Result<Self, NoetherError>;
1876}
1877
1878macro_rules! impl_riemann_roch_json {
1879    ($field:ty) => {
1880        impl DivisorJson for WeilDivisor<$field> {
1881            fn divisor_to_json(&self) -> Result<String, NoetherError> {
1882                Self::to_json(self)
1883            }
1884
1885            fn divisor_from_json(value: &str) -> Result<Self, NoetherError> {
1886                Self::from_json(value)
1887            }
1888        }
1889
1890        impl RiemannRochSpace<$field> {
1891            /// Serialize the source divisor; the canonical basis is derived evidence.
1892            pub fn to_json(&self) -> Result<String, NoetherError> {
1893                riemann_roch_to_json(self)
1894            }
1895
1896            /// Restore the divisor and recompute the deterministic section basis.
1897            pub fn from_json(value: &str) -> Result<Self, NoetherError> {
1898                riemann_roch_from_json(value)
1899            }
1900        }
1901    };
1902}
1903
1904impl_riemann_roch_json!(RationalField);
1905impl_riemann_roch_json!(FiniteField);
1906impl_riemann_roch_json!(RationalFunctionField<RationalField>);
1907impl_riemann_roch_json!(RationalFunctionField<FiniteField>);
1908impl_riemann_roch_json!(NumberField);
1909
1910fn compute_riemann_roch_space<R: Field>(
1911    divisor: &WeilDivisor<R>,
1912) -> Result<RiemannRochSpace<R>, CurveError> {
1913    if let Some(space) = try_weierstrass_riemann_roch_space(divisor)? {
1914        return Ok(space);
1915    }
1916    let curve = divisor.curve();
1917    let ring = curve.equation.ring();
1918    let curve_ideal = curve.scheme.defining_ideal();
1919    let irrelevant = ring.ideal(ring.generators()?)?;
1920    let positive = divisor.positive_part();
1921    let negative = divisor.negative_part();
1922    let positive_ideal = effective_divisor_ideal(&positive, &irrelevant)?;
1923    let negative_ideal = effective_divisor_ideal(&negative, &irrelevant)?;
1924
1925    let denominator_section = if positive.terms.is_empty() {
1926        ring.one()
1927    } else {
1928        let candidates = positive_ideal
1929            .generators()
1930            .iter()
1931            .filter_map(|generator| {
1932                let degree = generator.homogeneous_degree().ok().flatten()?;
1933                if generator.is_zero() || curve_ideal.contains(generator).ok()? || degree.sign() < 0
1934                {
1935                    return None;
1936                }
1937                Some((degree, generator.clone()))
1938            })
1939            .collect::<Vec<_>>();
1940        candidates
1941            .into_iter()
1942            .min_by(|(left, _), (right, _)| left.cmp(right))
1943            .map(|(_, generator)| generator)
1944            .ok_or(CurveError::InvalidWitness(
1945                "an effective divisor ideal contains a nonzero section off the curve ideal",
1946            ))?
1947    };
1948    let section_degree =
1949        denominator_section
1950            .homogeneous_degree()?
1951            .ok_or(CurveError::InvalidWitness(
1952                "the selected denominator section is homogeneous",
1953            ))?;
1954    let section_degree_u64 = integer_to_u64(&section_degree)?;
1955    let principal_section = ring.ideal([denominator_section.clone()])?;
1956    let complete_intersection = curve_ideal.sum(&principal_section)?;
1957    let residual = complete_intersection
1958        .colon(&positive_ideal)?
1959        .saturate(&irrelevant)?;
1960    let required_zeros = residual
1961        .product(&negative_ideal)?
1962        .sum(curve_ideal)?
1963        .saturate(&irrelevant)?;
1964
1965    let section_basis = homogeneous_ideal_component(&required_zeros, section_degree_u64)?;
1966    let numerator_sections =
1967        quotient_section_basis(curve_ideal, section_basis, section_degree_u64)?;
1968    let function_field = curve.function_field()?;
1969    let basis = numerator_sections
1970        .iter()
1971        .map(|numerator| function_field.homogeneous_fraction(numerator, &denominator_section))
1972        .collect::<Result<Vec<_>, _>>()?;
1973    Ok(RiemannRochSpace {
1974        divisor: divisor.clone(),
1975        basis,
1976        denominator_section,
1977        numerator_sections,
1978    })
1979}
1980
1981fn try_weierstrass_riemann_roch_space<R: Field>(
1982    divisor: &WeilDivisor<R>,
1983) -> Result<Option<RiemannRochSpace<R>>, CurveError> {
1984    if divisor.curve.presentation_kind() != CurvePresentationKind::SmoothProjectiveWeierstrass {
1985        return Ok(None);
1986    }
1987    let field = divisor.curve.base();
1988    let zero = field.zero();
1989    let one = field.one();
1990    let positive = divisor.positive_part();
1991    let pole_bound = match positive.terms.as_slice() {
1992        [] => 0,
1993        [(point, coefficient)]
1994            if point.rational_point().is_some_and(|point| {
1995                point.coordinates() == [zero.clone(), one.clone(), zero.clone()]
1996            }) =>
1997        {
1998            coefficient
1999                .to_u64_checked()
2000                .and_then(|value| usize::try_from(value).ok())
2001                .ok_or(CurveError::ResourceLimit {
2002                    resource: "Weierstrass pole bound",
2003                    limit: usize::MAX,
2004                })?
2005        }
2006        _ => return Ok(None),
2007    };
2008    let negative = divisor.negative_part();
2009    if negative
2010        .terms
2011        .iter()
2012        .any(|(point, _)| point.rational_point().is_none())
2013    {
2014        return Ok(None);
2015    }
2016
2017    let functions = divisor.curve.function_field()?;
2018    let x = functions.x()?;
2019    let y = functions.y()?;
2020    let ring = divisor.curve.equation.ring();
2021    let common_degree = u64::try_from(pole_bound).map_err(|_| CurveError::ResourceLimit {
2022        resource: "Weierstrass Riemann--Roch section degree",
2023        limit: usize::MAX,
2024    })?;
2025    let denominator_section =
2026        ring.from_terms([(small_exponents([0, 0, common_degree]), field.one())])?;
2027
2028    let mut ambient = vec![(0_usize, 0_usize, false, functions.one())];
2029    for power in 1..=pole_bound / 2 {
2030        ambient.push((2 * power, power, false, x.pow(power as u64)?));
2031    }
2032    if pole_bound >= 3 {
2033        for power in 0..=(pole_bound - 3) / 2 {
2034            let function = functions.checked_mul(&x.pow(power as u64)?, &y)?;
2035            ambient.push((2 * power + 3, power, true, function));
2036        }
2037    }
2038    ambient.sort_by_key(|(pole_order, x_power, has_y, _)| (*pole_order, *has_y, *x_power));
2039
2040    let ambient_numerators = ambient
2041        .iter()
2042        .map(|(_, x_power, has_y, _)| {
2043            let x_power = u64::try_from(*x_power).map_err(|_| CurveError::ResourceLimit {
2044                resource: "Weierstrass monomial exponent",
2045                limit: usize::MAX,
2046            })?;
2047            let y_power = u64::from(*has_y);
2048            let used_degree = x_power + y_power;
2049            let z_power =
2050                common_degree
2051                    .checked_sub(used_degree)
2052                    .ok_or(CurveError::InvalidWitness(
2053                        "a bounded-pole Weierstrass monomial homogenizes in the common degree",
2054                    ))?;
2055            ring.from_terms([(small_exponents([x_power, y_power, z_power]), field.one())])
2056                .map_err(Into::into)
2057        })
2058        .collect::<Result<Vec<_>, CurveError>>()?;
2059
2060    let condition_count = negative.terms.iter().try_fold(0_usize, |sum, (_, order)| {
2061        order
2062            .to_u64_checked()
2063            .and_then(|order| usize::try_from(order).ok())
2064            .and_then(|order| sum.checked_add(order))
2065            .ok_or(CurveError::ResourceLimit {
2066                resource: "Weierstrass vanishing-condition count",
2067                limit: usize::MAX,
2068            })
2069    })?;
2070    let mut rows = Vec::with_capacity(condition_count);
2071    for (point, order) in &negative.terms {
2072        let point = point.rational_point().ok_or(CurveError::InvalidWitness(
2073            "the Weierstrass fast path uses rational support",
2074        ))?;
2075        let order = order
2076            .to_u64_checked()
2077            .and_then(|order| usize::try_from(order).ok())
2078            .ok_or(CurveError::ResourceLimit {
2079                resource: "Weierstrass vanishing order",
2080                limit: usize::MAX,
2081            })?;
2082        // This ambient basis contains only `x^i` and `x^i y`. A cubic chart
2083        // consumes at most six absolute terms when its patch coordinate is
2084        // inverted, so sixteen guard terms suffice without invoking the much
2085        // larger generic rational-function allowance.
2086        let precision = order
2087            .checked_add(16)
2088            .filter(|precision| *precision <= 1_000_000)
2089            .ok_or(CurveError::ResourceLimit {
2090                resource: "Weierstrass local expansion precision",
2091                limit: 1_000_000,
2092            })?;
2093        let coordinate_germs = projective_coordinate_germs(point, precision)?;
2094        let germs = ambient
2095            .iter()
2096            .map(|(_, _, _, function)| {
2097                curve_function_germ_from_coordinates(function, &coordinate_germs)
2098            })
2099            .collect::<Result<Vec<_>, _>>()?;
2100        for local_order in 0..order {
2101            let exponent = i64::try_from(local_order).map_err(|_| CurveError::ResourceLimit {
2102                resource: "local vanishing exponent",
2103                limit: usize::MAX,
2104            })?;
2105            rows.push(
2106                germs
2107                    .iter()
2108                    .map(|germ| germ.coefficient(exponent))
2109                    .collect::<Vec<_>>(),
2110            );
2111        }
2112    }
2113    let operator = if rows.is_empty() {
2114        Matrix::zeros(field.clone(), 0, ambient.len())
2115    } else {
2116        Matrix::from_rows(field.clone(), rows)?
2117    };
2118    let kernel = operator.kernel()?;
2119    let numerator_sections = kernel
2120        .basis()
2121        .iter()
2122        .map(|vector| {
2123            let mut numerator = ring.zero();
2124            for (coefficient, monomial) in vector.values().zip(&ambient_numerators) {
2125                numerator = (&numerator + &monomial.scalar_mul(&coefficient)?)?;
2126            }
2127            Ok(numerator)
2128        })
2129        .collect::<Result<Vec<_>, CurveError>>()?;
2130    let basis = numerator_sections
2131        .iter()
2132        .map(|numerator| functions.homogeneous_fraction(numerator, &denominator_section))
2133        .collect::<Result<Vec<_>, _>>()?;
2134    Ok(Some(RiemannRochSpace {
2135        divisor: divisor.clone(),
2136        basis,
2137        denominator_section,
2138        numerator_sections,
2139    }))
2140}
2141
2142fn effective_divisor_ideal<R: Field>(
2143    divisor: &WeilDivisor<R>,
2144    irrelevant: &super::Ideal<PolynomialRing<R>>,
2145) -> Result<super::Ideal<PolynomialRing<R>>, CurveError> {
2146    let curve_ideal = divisor.curve.scheme.defining_ideal();
2147    let ring = curve_ideal.ring();
2148    let mut product = ring.ideal([ring.one()])?;
2149    for (point, coefficient) in &divisor.terms {
2150        if coefficient.is_negative() {
2151            return Err(CurveError::InvalidWitness(
2152                "an effective divisor has nonnegative coefficients",
2153            ));
2154        }
2155        let exponent = coefficient
2156            .to_u64_checked()
2157            .ok_or(CurveError::ResourceLimit {
2158                resource: "divisor multiplicity exponent",
2159                limit: usize::MAX,
2160            })?;
2161        product = product.product(&point.prime_ideal.ideal().pow(exponent)?)?;
2162    }
2163    product
2164        .sum(curve_ideal)?
2165        .saturate(irrelevant)
2166        .map_err(Into::into)
2167}
2168
2169fn homogeneous_ideal_component<R: Field>(
2170    ideal: &super::Ideal<PolynomialRing<R>>,
2171    degree: u64,
2172) -> Result<Vec<Polynomial<R>>, CurveError> {
2173    const MAX_SECTION_MONOMIALS: usize = 100_000;
2174    let ring = ideal.ring();
2175    let monomials = ring.homogeneous_monomials(degree, MAX_SECTION_MONOMIALS)?;
2176    let exponents = monomials
2177        .iter()
2178        .map(|monomial| {
2179            monomial
2180                .terms()
2181                .next()
2182                .map(|(exponent, _)| exponent.to_vec())
2183                .ok_or(CurveError::InvalidWitness(
2184                    "a homogeneous monomial has one nonzero term",
2185                ))
2186        })
2187        .collect::<Result<Vec<_>, _>>()?;
2188    let normal_forms = monomials
2189        .iter()
2190        .map(|monomial| ideal.normal_form(monomial).map_err(Into::into))
2191        .collect::<Result<Vec<_>, CurveError>>()?;
2192    let rows = exponents
2193        .iter()
2194        .map(|exponent| {
2195            normal_forms
2196                .iter()
2197                .map(|normal_form| normal_form.coefficient(exponent).map_err(Into::into))
2198                .collect::<Result<Vec<_>, CurveError>>()
2199        })
2200        .collect::<Result<Vec<_>, _>>()?;
2201    let operator = Matrix::from_rows(ring.coefficient_ring().clone(), rows)?;
2202    let kernel = operator.kernel()?;
2203    kernel
2204        .basis()
2205        .iter()
2206        .map(|vector| {
2207            ring.from_terms(exponents.iter().cloned().zip(vector.values()))
2208                .map_err(Into::into)
2209        })
2210        .collect()
2211}
2212
2213fn quotient_section_basis<R: Field>(
2214    curve_ideal: &super::Ideal<PolynomialRing<R>>,
2215    sections: Vec<Polynomial<R>>,
2216    degree: u64,
2217) -> Result<Vec<Polynomial<R>>, CurveError> {
2218    const MAX_SECTION_MONOMIALS: usize = 100_000;
2219    let ring = curve_ideal.ring();
2220    let monomials = ring.homogeneous_monomials(degree, MAX_SECTION_MONOMIALS)?;
2221    let exponents = monomials
2222        .iter()
2223        .map(|monomial| {
2224            monomial
2225                .terms()
2226                .next()
2227                .map(|(exponent, _)| exponent.to_vec())
2228                .ok_or(CurveError::InvalidWitness(
2229                    "a homogeneous monomial has one nonzero term",
2230                ))
2231        })
2232        .collect::<Result<Vec<_>, _>>()?;
2233    let mut basis = Vec::new();
2234    let mut rows: Vec<Vec<R::Element>> = Vec::new();
2235    let mut rank = 0;
2236    for section in sections {
2237        let reduced = curve_ideal.normal_form(&section)?;
2238        if reduced.is_zero() {
2239            continue;
2240        }
2241        let row = exponents
2242            .iter()
2243            .map(|exponent| reduced.coefficient(exponent).map_err(Into::into))
2244            .collect::<Result<Vec<_>, CurveError>>()?;
2245        let mut candidate_rows = rows.clone();
2246        candidate_rows.push(row.clone());
2247        let candidate_rank =
2248            Matrix::from_rows(ring.coefficient_ring().clone(), candidate_rows)?.rank()?;
2249        if candidate_rank > rank {
2250            rows.push(row);
2251            basis.push(section);
2252            rank = candidate_rank;
2253        }
2254    }
2255    Ok(basis)
2256}
2257
2258/// Exact order of a function in a discrete valuation.
2259#[derive(Debug, Clone, PartialEq, Eq)]
2260pub enum CurveFunctionOrder {
2261    /// Finite signed order: positive for a zero and negative for a pole.
2262    Finite(Integer),
2263    /// The identically zero function has infinite order at every point.
2264    Infinite,
2265}
2266
2267/// Typed evaluation of a curve function at a rational point.
2268#[derive(Debug, Clone, PartialEq, Eq)]
2269pub enum CurveFunctionEvaluation<R: Field> {
2270    /// A nonzero regular value in the constant field.
2271    Regular(R::Element),
2272    /// A zero, with `None` only for the identically zero function.
2273    Zero {
2274        /// Positive finite vanishing order, or `None` for the zero function.
2275        order: Option<Integer>,
2276    },
2277    /// A pole of the stated positive order.
2278    Pole {
2279        /// Positive pole order.
2280        order: Integer,
2281    },
2282}
2283
2284/// The canonical function field of one checked projective plane curve.
2285///
2286/// A deterministic affine chart chooses coordinates `x` and `y`, views the
2287/// equation as a monic polynomial in `y` over `K(x)`, and stores every element
2288/// as its unique representative of `y`-degree below the extension degree.
2289#[derive(Clone, PartialEq, Eq)]
2290pub struct CurveFunctionField<R: Field> {
2291    curve: Curve<R>,
2292    transcendental_coordinate: usize,
2293    algebraic_coordinate: usize,
2294    patch_coordinate: usize,
2295    rational_field: RationalFunctionField<R>,
2296    extension_ring: PolynomialRing<RationalFunctionField<R>>,
2297    modulus: Polynomial<RationalFunctionField<R>>,
2298    extension_degree: usize,
2299}
2300
2301impl<R: Field> CurveFunctionField<R> {
2302    fn new(curve: Curve<R>) -> Result<Self, CurveError> {
2303        let preferred = match curve.presentation {
2304            CurvePresentation::Weierstrass { .. } => vec![(0, 1, 2)],
2305            CurvePresentation::Plane => coordinate_presentations(),
2306        };
2307        for (x_index, y_index, patch_index) in preferred {
2308            if let Some(field) =
2309                Self::with_coordinates(curve.clone(), x_index, y_index, patch_index)?
2310            {
2311                return Ok(field);
2312            }
2313        }
2314        Err(CurveError::NoFunctionFieldPresentation)
2315    }
2316
2317    fn with_coordinates(
2318        curve: Curve<R>,
2319        x_index: usize,
2320        y_index: usize,
2321        patch_index: usize,
2322    ) -> Result<Option<Self>, CurveError> {
2323        let names = curve.equation.ring().variables();
2324        let rational_field =
2325            RationalFunctionField::new(curve.base().clone(), names[x_index].clone())?;
2326        let extension_ring = PolynomialRing::new(
2327            rational_field.clone(),
2328            [names[y_index].clone()],
2329            MonomialOrder::Lexicographic,
2330        )?;
2331        let coefficient_ring = rational_field.polynomial_ring();
2332        let mut by_y = std::collections::BTreeMap::new();
2333        for (exponent, coefficient) in curve.equation.terms() {
2334            let y_power = exponent[y_index].clone();
2335            by_y.entry(y_power)
2336                .or_insert_with(Vec::new)
2337                .push((vec![exponent[x_index].clone()], coefficient.clone()));
2338            let _ = patch_index;
2339        }
2340        let Some(y_degree) = by_y.keys().next_back().cloned() else {
2341            return Ok(None);
2342        };
2343        if y_degree.is_zero() {
2344            return Ok(None);
2345        }
2346        let extension_degree = integer_to_usize(&y_degree)?;
2347        let mut terms = Vec::with_capacity(by_y.len());
2348        for (y_power, coefficient_terms) in by_y {
2349            let coefficient = coefficient_ring.from_terms(coefficient_terms)?;
2350            let coefficient = rational_field.from_polynomial(coefficient)?;
2351            terms.push((vec![y_power], coefficient));
2352        }
2353        let modulus = extension_ring.from_terms(terms)?.monic()?;
2354        Ok(Some(Self {
2355            curve,
2356            transcendental_coordinate: x_index,
2357            algebraic_coordinate: y_index,
2358            patch_coordinate: patch_index,
2359            rational_field,
2360            extension_ring,
2361            modulus,
2362            extension_degree,
2363        }))
2364    }
2365
2366    /// Source curve.
2367    pub fn curve(&self) -> &Curve<R> {
2368        &self.curve
2369    }
2370
2371    /// Coordinate used as the transcendental generator.
2372    pub fn transcendental_coordinate(&self) -> usize {
2373        self.transcendental_coordinate
2374    }
2375
2376    /// Coordinate algebraic over the rational subfield.
2377    pub fn algebraic_coordinate(&self) -> usize {
2378        self.algebraic_coordinate
2379    }
2380
2381    /// Homogeneous coordinate set to one in the selected affine chart.
2382    pub fn patch_coordinate(&self) -> usize {
2383        self.patch_coordinate
2384    }
2385
2386    /// Rational subfield `K(x)` retained by this presentation.
2387    pub fn rational_subfield(&self) -> &RationalFunctionField<R> {
2388        &self.rational_field
2389    }
2390
2391    /// Monic defining polynomial in `K(x)[y]`.
2392    pub fn modulus(&self) -> &Polynomial<RationalFunctionField<R>> {
2393        &self.modulus
2394    }
2395
2396    /// Degree over the retained rational subfield.
2397    pub fn extension_degree(&self) -> usize {
2398        self.extension_degree
2399    }
2400
2401    /// The affine coordinate `x` in the chosen chart.
2402    pub fn x(&self) -> Result<CurveFunction<R>, CurveError> {
2403        self.from_representative(
2404            self.extension_ring
2405                .constant(self.rational_field.generator())?,
2406        )
2407    }
2408
2409    /// The affine coordinate `y` in the chosen chart.
2410    pub fn y(&self) -> Result<CurveFunction<R>, CurveError> {
2411        self.from_representative(self.extension_ring.generator(0)?)
2412    }
2413
2414    /// The ratio of a homogeneous coordinate to the selected patch coordinate.
2415    pub fn coordinate(&self, coordinate: usize) -> Result<CurveFunction<R>, CurveError> {
2416        if coordinate == self.transcendental_coordinate {
2417            self.x()
2418        } else if coordinate == self.algebraic_coordinate {
2419            self.y()
2420        } else if coordinate == self.patch_coordinate {
2421            Ok(self.one())
2422        } else {
2423            Err(CurveError::InvalidWitness(
2424                "homogeneous coordinate index is outside the plane presentation",
2425            ))
2426        }
2427    }
2428
2429    /// Embed one constant-field element.
2430    pub fn constant(&self, value: R::Element) -> Result<CurveFunction<R>, CurveError> {
2431        let coefficient = self.rational_field.constant(value)?;
2432        self.from_representative(self.extension_ring.constant(coefficient)?)
2433    }
2434
2435    /// Restrict one homogeneous-coordinate polynomial to the curve's function field.
2436    ///
2437    /// Homogeneous coordinates are interpreted as ratios to the selected patch
2438    /// coordinate. For a homogeneous polynomial this differs from the original
2439    /// section by the corresponding patch-coordinate power, exactly as needed
2440    /// when forming a ratio of equal-degree sections.
2441    pub fn from_homogeneous_polynomial(
2442        &self,
2443        polynomial: &Polynomial<R>,
2444    ) -> Result<CurveFunction<R>, CurveError> {
2445        if polynomial.ring() != self.curve.equation.ring() {
2446            return Err(ContextMismatchError {
2447                expected: self.curve.equation.ring().to_string(),
2448                actual: polynomial.ring().to_string(),
2449            }
2450            .into());
2451        }
2452        let x = self.x()?;
2453        let y = self.y()?;
2454        let mut result = self.zero();
2455        for (exponent, coefficient) in polynomial.terms() {
2456            let mut term = self.constant(coefficient.clone())?;
2457            let x_power = integer_to_u64(&exponent[self.transcendental_coordinate])?;
2458            let y_power = integer_to_u64(&exponent[self.algebraic_coordinate])?;
2459            term = self.checked_mul(&term, &x.pow(x_power)?)?;
2460            term = self.checked_mul(&term, &y.pow(y_power)?)?;
2461            result = self.checked_add(&result, &term)?;
2462        }
2463        Ok(result)
2464    }
2465
2466    /// Ratio of two equal-degree homogeneous sections restricted to the curve.
2467    pub fn homogeneous_fraction(
2468        &self,
2469        numerator: &Polynomial<R>,
2470        denominator: &Polynomial<R>,
2471    ) -> Result<CurveFunction<R>, CurveError> {
2472        let numerator_degree = numerator.homogeneous_degree()?;
2473        let denominator_degree = denominator.homogeneous_degree()?;
2474        if numerator_degree.is_none() || numerator_degree != denominator_degree {
2475            return Err(CurveError::InvalidWitness(
2476                "a projective function ratio uses homogeneous sections of equal degree",
2477            ));
2478        }
2479        let numerator = self.from_homogeneous_polynomial(numerator)?;
2480        let denominator = self.from_homogeneous_polynomial(denominator)?;
2481        numerator.checked_div(&denominator)
2482    }
2483
2484    /// Reduce one polynomial in the retained algebraic coordinate modulo the curve equation.
2485    pub fn from_representative(
2486        &self,
2487        value: Polynomial<RationalFunctionField<R>>,
2488    ) -> Result<CurveFunction<R>, CurveError> {
2489        if !self.extension_ring.contains(&value) {
2490            return Err(ContextMismatchError {
2491                expected: self.extension_ring.to_string(),
2492                actual: value.ring().to_string(),
2493            }
2494            .into());
2495        }
2496        let (_, representative) = value.div_rem(&self.modulus)?;
2497        Ok(CurveFunction {
2498            field: self.clone(),
2499            representative,
2500        })
2501    }
2502
2503    fn require(&self, value: &CurveFunction<R>) -> Result<(), CurveError> {
2504        if self == &value.field {
2505            Ok(())
2506        } else {
2507            Err(ContextMismatchError {
2508                expected: Self::TYPE_ID.to_owned(),
2509                actual: "curve function belongs to another function field".to_owned(),
2510            }
2511            .into())
2512        }
2513    }
2514
2515    fn checked_add(
2516        &self,
2517        left: &CurveFunction<R>,
2518        right: &CurveFunction<R>,
2519    ) -> Result<CurveFunction<R>, CurveError> {
2520        self.require(left)?;
2521        self.require(right)?;
2522        self.from_representative((&left.representative + &right.representative)?)
2523    }
2524
2525    fn checked_sub(
2526        &self,
2527        left: &CurveFunction<R>,
2528        right: &CurveFunction<R>,
2529    ) -> Result<CurveFunction<R>, CurveError> {
2530        self.require(left)?;
2531        self.require(right)?;
2532        self.from_representative((&left.representative - &right.representative)?)
2533    }
2534
2535    fn checked_mul(
2536        &self,
2537        left: &CurveFunction<R>,
2538        right: &CurveFunction<R>,
2539    ) -> Result<CurveFunction<R>, CurveError> {
2540        self.require(left)?;
2541        self.require(right)?;
2542        self.from_representative((&left.representative * &right.representative)?)
2543    }
2544
2545    fn checked_neg(&self, value: &CurveFunction<R>) -> Result<CurveFunction<R>, CurveError> {
2546        self.require(value)?;
2547        self.from_representative((-&value.representative)?)
2548    }
2549
2550    fn checked_inverse(&self, value: &CurveFunction<R>) -> Result<CurveFunction<R>, CurveError> {
2551        self.require(value)?;
2552        if value.is_zero() {
2553            return Err(CurveError::DivisionByZero);
2554        }
2555        let (gcd, coefficient) = extended_gcd_coefficient(&self.modulus, &value.representative)?;
2556        let degree = gcd.degree(Some(0))?;
2557        if degree.as_ref().is_some_and(|degree| !degree.is_zero()) || gcd.is_zero() {
2558            return Err(CurveError::InvalidWitness(
2559                "the checked integral curve equation did not yield a field quotient",
2560            ));
2561        }
2562        let scalar = gcd.constant_coefficient();
2563        let scalar_inverse = self
2564            .rational_field
2565            .inverse(&scalar)
2566            .map_err(|_| CurveError::InvalidWitness("extended gcd returned zero"))?;
2567        self.from_representative(coefficient.scalar_mul(&scalar_inverse)?)
2568    }
2569
2570    /// Recheck the source curve, monic modulus, degree, and coordinate conversion.
2571    pub fn verify(&self) -> Result<(), VerificationError> {
2572        self.curve.verify()?;
2573        let rebuilt = Self::with_coordinates(
2574            self.curve.clone(),
2575            self.transcendental_coordinate,
2576            self.algebraic_coordinate,
2577            self.patch_coordinate,
2578        )
2579        .map_err(|_| VerificationError::InvalidWitness {
2580            claim: "curve function-field presentation reconstructs",
2581        })?
2582        .ok_or(VerificationError::InvalidWitness {
2583            claim: "curve function-field equation has positive algebraic degree",
2584        })?;
2585        if &rebuilt == self {
2586            Ok(())
2587        } else {
2588            Err(VerificationError::InvalidWitness {
2589                claim: "curve function-field presentation is canonical",
2590            })
2591        }
2592    }
2593
2594    /// Serialize the owning curve; the canonical field presentation is derived.
2595    pub fn to_json(&self) -> Result<String, NoetherError>
2596    where
2597        R: Serialize,
2598        R::Element: Serialize,
2599    {
2600        encode_typed_json(
2601            Self::TYPE_ID,
2602            [("curve", nested_json(self.curve.to_json()?)?)],
2603        )
2604    }
2605
2606    /// Restore the curve and deterministically rebuild its function field.
2607    pub fn from_json(value: &str) -> Result<Self, NoetherError>
2608    where
2609        R: DeserializeOwned,
2610        R::Element: DeserializeOwned,
2611    {
2612        let mut object = decode_typed_json(value, Self::TYPE_ID, &["curve"])?;
2613        let curve = Curve::<R>::from_json(&encode_nested_field(&mut object, "curve")?)?;
2614        curve.function_field().map_err(curve_serialization_error)
2615    }
2616}
2617
2618impl<R: Field> fmt::Debug for CurveFunctionField<R> {
2619    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2620        formatter
2621            .debug_struct("CurveFunctionField")
2622            .field("base", &R::TYPE_ID)
2623            .field("x", &self.transcendental_coordinate)
2624            .field("y", &self.algebraic_coordinate)
2625            .field("patch", &self.patch_coordinate)
2626            .field("degree", &self.extension_degree)
2627            .finish()
2628    }
2629}
2630
2631impl<R: Field> fmt::Display for CurveFunctionField<R> {
2632    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2633        write!(
2634            formatter,
2635            "CurveFunctionField<{}>(degree={})",
2636            R::TYPE_ID,
2637            self.extension_degree
2638        )
2639    }
2640}
2641
2642impl<R: Field> SemanticType for CurveFunctionField<R> {
2643    const TYPE_ID: &'static str = "noether/commalg/CurveFunctionField@1";
2644
2645    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
2646        let mut output = self.curve.canonical_bytes()?;
2647        for coordinate in [
2648            self.transcendental_coordinate,
2649            self.algebraic_coordinate,
2650            self.patch_coordinate,
2651        ] {
2652            output.extend_from_slice(
2653                &u64::try_from(coordinate)
2654                    .map_err(|_| NoetherError::Serialization("coordinate index overflow".into()))?
2655                    .to_be_bytes(),
2656            );
2657        }
2658        append_component(&mut output, &self.modulus.canonical_bytes()?)?;
2659        Ok(output)
2660    }
2661
2662    fn describe() -> &'static TypeDescription {
2663        &CURVE_FUNCTION_FIELD_DESCRIPTION
2664    }
2665}
2666
2667impl<R: Field> Ring for CurveFunctionField<R> {
2668    type Element = CurveFunction<R>;
2669
2670    fn contains(&self, value: &Self::Element) -> bool {
2671        self == &value.field
2672    }
2673
2674    fn zero(&self) -> Self::Element {
2675        CurveFunction {
2676            field: self.clone(),
2677            representative: self.extension_ring.zero(),
2678        }
2679    }
2680
2681    fn one(&self) -> Self::Element {
2682        CurveFunction {
2683            field: self.clone(),
2684            representative: self.extension_ring.one(),
2685        }
2686    }
2687
2688    fn add(
2689        &self,
2690        a: &Self::Element,
2691        b: &Self::Element,
2692    ) -> Result<Self::Element, ContextMismatchError> {
2693        self.checked_add(a, b).map_err(curve_context_error::<R>)
2694    }
2695
2696    fn sub(
2697        &self,
2698        a: &Self::Element,
2699        b: &Self::Element,
2700    ) -> Result<Self::Element, ContextMismatchError> {
2701        self.checked_sub(a, b).map_err(curve_context_error::<R>)
2702    }
2703
2704    fn mul(
2705        &self,
2706        a: &Self::Element,
2707        b: &Self::Element,
2708    ) -> Result<Self::Element, ContextMismatchError> {
2709        self.checked_mul(a, b).map_err(curve_context_error::<R>)
2710    }
2711
2712    fn neg(&self, value: &Self::Element) -> Result<Self::Element, ContextMismatchError> {
2713        self.checked_neg(value).map_err(curve_context_error::<R>)
2714    }
2715}
2716
2717impl<R: Field> CommutativeRing for CurveFunctionField<R> {}
2718impl<R: Field> IntegralDomain for CurveFunctionField<R> {}
2719
2720impl<R: Field> UnitInversion for CurveFunctionField<R> {
2721    fn inverse_unit(&self, value: &Self::Element) -> Result<Self::Element, UnitError> {
2722        self.inverse(value).map_err(UnitError::from)
2723    }
2724}
2725
2726impl<R: Field> Field for CurveFunctionField<R> {
2727    fn inverse(&self, value: &Self::Element) -> Result<Self::Element, InversionError> {
2728        self.checked_inverse(value).map_err(|error| match error {
2729            CurveError::DivisionByZero => DivisionByZeroError.into(),
2730            CurveError::ContextMismatch(error) => error.into(),
2731            error => InversionError::ContextMismatch(curve_context_error::<R>(error)),
2732        })
2733    }
2734}
2735
2736/// One canonical element of a checked curve function field.
2737#[derive(Clone, PartialEq, Eq)]
2738pub struct CurveFunction<R: Field> {
2739    field: CurveFunctionField<R>,
2740    representative: Polynomial<RationalFunctionField<R>>,
2741}
2742
2743impl<R: Field> CurveFunction<R> {
2744    /// Owning curve function field.
2745    pub fn field(&self) -> &CurveFunctionField<R> {
2746        &self.field
2747    }
2748
2749    /// Unique representative of algebraic-coordinate degree below the field degree.
2750    pub fn representative(&self) -> &Polynomial<RationalFunctionField<R>> {
2751        &self.representative
2752    }
2753
2754    /// Canonical equal-degree homogeneous numerator and denominator sections.
2755    ///
2756    /// The zero function is represented by `(0,1)`. Every nonzero function is
2757    /// first reduced in the admitted `K(x)[y]` presentation and then cleared
2758    /// of coefficient denominators deterministically.
2759    pub fn homogeneous_representation(&self) -> Result<(Polynomial<R>, Polynomial<R>), CurveError> {
2760        if self.is_zero() {
2761            let ring = self.field.curve.equation.ring();
2762            return Ok((ring.zero(), ring.one()));
2763        }
2764        canonical_projective_fraction(self)
2765    }
2766
2767    /// Whether this function is zero.
2768    pub fn is_zero(&self) -> bool {
2769        self.representative.is_zero()
2770    }
2771
2772    /// Whether this function is one.
2773    pub fn is_one(&self) -> bool {
2774        self.representative == self.field.extension_ring.one()
2775    }
2776
2777    /// Recover the constant-field value when this function is constant.
2778    pub fn as_constant(&self) -> Result<Option<R::Element>, CurveError> {
2779        if self.is_zero() {
2780            return Ok(Some(self.field.curve.base().zero()));
2781        }
2782        if self
2783            .representative
2784            .terms()
2785            .any(|(exponent, _)| !exponent[0].is_zero())
2786        {
2787            return Ok(None);
2788        }
2789        let coefficient = self.representative.coefficient(&[Integer::zero()])?;
2790        let numerator_degree = coefficient.numerator().degree(Some(0))?;
2791        let denominator_degree = coefficient.denominator().degree(Some(0))?;
2792        if numerator_degree
2793            .as_ref()
2794            .is_some_and(|degree| !degree.is_zero())
2795            || denominator_degree
2796                .as_ref()
2797                .is_some_and(|degree| !degree.is_zero())
2798        {
2799            return Ok(None);
2800        }
2801        let numerator = coefficient.numerator().constant_coefficient();
2802        let denominator = coefficient.denominator().constant_coefficient();
2803        let inverse = self
2804            .field
2805            .curve
2806            .base()
2807            .inverse(&denominator)
2808            .map_err(|_| CurveError::InvalidWitness("a reduced denominator is nonzero"))?;
2809        self.field
2810            .curve
2811            .base()
2812            .mul(&numerator, &inverse)
2813            .map(Some)
2814            .map_err(Into::into)
2815    }
2816
2817    /// Raise to a nonnegative machine-word power.
2818    pub fn pow(&self, mut exponent: u64) -> Result<Self, CurveError> {
2819        let mut result = self.field.one();
2820        let mut base = self.clone();
2821        while exponent > 0 {
2822            if exponent & 1 == 1 {
2823                result = self.field.checked_mul(&result, &base)?;
2824            }
2825            exponent >>= 1;
2826            if exponent > 0 {
2827                base = self.field.checked_mul(&base, &base)?;
2828            }
2829        }
2830        Ok(result)
2831    }
2832
2833    /// Checked multiplicative inverse.
2834    pub fn checked_inverse(&self) -> Result<Self, CurveError> {
2835        self.field.checked_inverse(self)
2836    }
2837
2838    /// Divide by another function from this exact field.
2839    pub fn checked_div(&self, divisor: &Self) -> Result<Self, CurveError> {
2840        let inverse = self.field.checked_inverse(divisor)?;
2841        self.field.checked_mul(self, &inverse)
2842    }
2843
2844    /// Exact discrete-valuation order at a checked rational point.
2845    ///
2846    /// The local implicit-function expansion is deliberately bounded. Use
2847    /// [`order_at_with_limit`](Self::order_at_with_limit) when a larger order
2848    /// is expected.
2849    pub fn order_at(&self, point: &CurvePoint<R>) -> Result<CurveFunctionOrder, CurveError> {
2850        self.order_at_with_limit(point, 256)
2851    }
2852
2853    /// Exact order at a rational point, searching through `limit` local terms.
2854    pub fn order_at_with_limit(
2855        &self,
2856        point: &CurvePoint<R>,
2857        limit: usize,
2858    ) -> Result<CurveFunctionOrder, CurveError> {
2859        if point.curve() != self.field.curve() {
2860            return Err(ContextMismatchError {
2861                expected: Curve::<R>::TYPE_ID.to_owned(),
2862                actual: "evaluation point lies on another curve".to_owned(),
2863            }
2864            .into());
2865        }
2866        if self.is_zero() {
2867            return Ok(CurveFunctionOrder::Infinite);
2868        }
2869        let (order, _) = self.finite_order_germ(point, limit)?;
2870        Ok(CurveFunctionOrder::Finite(Integer::from(order)))
2871    }
2872
2873    /// Evaluate at a rational point with distinct regular, zero, and pole results.
2874    pub fn evaluate_at(
2875        &self,
2876        point: &CurvePoint<R>,
2877    ) -> Result<CurveFunctionEvaluation<R>, CurveError> {
2878        self.evaluate_at_with_limit(point, 256)
2879    }
2880
2881    /// Typed evaluation, searching through the supplied local-order bound.
2882    pub fn evaluate_at_with_limit(
2883        &self,
2884        point: &CurvePoint<R>,
2885        limit: usize,
2886    ) -> Result<CurveFunctionEvaluation<R>, CurveError> {
2887        if point.curve() != self.field.curve() {
2888            return Err(ContextMismatchError {
2889                expected: Curve::<R>::TYPE_ID.to_owned(),
2890                actual: "evaluation point lies on another curve".to_owned(),
2891            }
2892            .into());
2893        }
2894        if self.is_zero() {
2895            return Ok(CurveFunctionEvaluation::Zero { order: None });
2896        }
2897        // A homogeneous equal-degree fraction can be evaluated directly when
2898        // neither section vanishes. This is both the canonical projective
2899        // evaluation and an important fast path over symbolic coefficient
2900        // fields, where constructing hundreds of local series coefficients
2901        // would be needlessly expensive for a regular nonzero value.
2902        let (numerator, denominator) = self.homogeneous_representation()?;
2903        let numerator_value = numerator.evaluate(point.coordinates())?;
2904        let denominator_value = denominator.evaluate(point.coordinates())?;
2905        let zero = self.field.curve.base().zero();
2906        if numerator_value != zero && denominator_value != zero {
2907            let inverse = self
2908                .field
2909                .curve
2910                .base()
2911                .inverse(&denominator_value)
2912                .map_err(|_| {
2913                    CurveError::InvalidWitness("a nonzero evaluated denominator is invertible")
2914                })?;
2915            let value = self.field.curve.base().mul(&numerator_value, &inverse)?;
2916            return Ok(CurveFunctionEvaluation::Regular(value));
2917        }
2918        let (order, germ) = self.finite_order_germ(point, limit)?;
2919        if order > 0 {
2920            Ok(CurveFunctionEvaluation::Zero {
2921                order: Some(Integer::from(order)),
2922            })
2923        } else if order < 0 {
2924            Ok(CurveFunctionEvaluation::Pole {
2925                order: Integer::from(-order),
2926            })
2927        } else {
2928            let value = germ.coefficient(0);
2929            if value == self.field.curve.base().zero() {
2930                return Err(CurveError::InvalidWitness(
2931                    "a valuation-zero germ has nonzero constant coefficient",
2932                ));
2933            }
2934            Ok(CurveFunctionEvaluation::Regular(value))
2935        }
2936    }
2937
2938    fn finite_order_germ(
2939        &self,
2940        point: &CurvePoint<R>,
2941        limit: usize,
2942    ) -> Result<(i64, LaurentGerm<R>), CurveError> {
2943        let mut searched = limit.min(8);
2944        loop {
2945            let germ = curve_function_germ(self, point, local_work_precision(self, searched)?)?;
2946            if let Some(order) = germ.valuation() {
2947                if order.unsigned_abs() > limit as u64 {
2948                    return Err(CurveError::LocalExpansionLimit { limit });
2949                }
2950                return Ok((order, germ));
2951            }
2952            if searched == limit {
2953                return Err(CurveError::LocalExpansionLimit { limit });
2954            }
2955            searched = searched.saturating_mul(2).max(1).min(limit);
2956        }
2957    }
2958
2959    /// Recompute reduction modulo the defining equation.
2960    pub fn verify(&self) -> Result<(), VerificationError> {
2961        self.field.verify()?;
2962        let rebuilt = self
2963            .field
2964            .from_representative(self.representative.clone())
2965            .map_err(|_| VerificationError::InvalidWitness {
2966                claim: "curve function representative reduces in its owning field",
2967            })?;
2968        if &rebuilt == self {
2969            Ok(())
2970        } else {
2971            Err(VerificationError::InvalidWitness {
2972                claim: "curve function representative has degree below the defining equation",
2973            })
2974        }
2975    }
2976
2977    /// Serialize a canonical equal-degree homogeneous fraction on its curve.
2978    pub fn to_json(&self) -> Result<String, NoetherError>
2979    where
2980        R: Serialize,
2981        R::Element: Serialize,
2982    {
2983        let (numerator, denominator) = self
2984            .homogeneous_representation()
2985            .map_err(curve_serialization_error)?;
2986        encode_typed_json(
2987            Self::TYPE_ID,
2988            [
2989                ("field", nested_json(self.field.to_json()?)?),
2990                (
2991                    "numerator",
2992                    serde_json::to_value(numerator).map_err(serialization_error)?,
2993                ),
2994                (
2995                    "denominator",
2996                    serde_json::to_value(denominator).map_err(serialization_error)?,
2997                ),
2998            ],
2999        )
3000    }
3001
3002    /// Restore, reduce, and reject a noncanonical homogeneous presentation.
3003    pub fn from_json(value: &str) -> Result<Self, NoetherError>
3004    where
3005        R: DeserializeOwned,
3006        R::Element: DeserializeOwned,
3007    {
3008        let mut object =
3009            decode_typed_json(value, Self::TYPE_ID, &["field", "numerator", "denominator"])?;
3010        let field =
3011            CurveFunctionField::<R>::from_json(&encode_nested_field(&mut object, "field")?)?;
3012        let numerator: Polynomial<R> = decode_field(&mut object, "numerator")?;
3013        let denominator: Polynomial<R> = decode_field(&mut object, "denominator")?;
3014        let rebuilt = if numerator.is_zero() && denominator.is_one() {
3015            field.zero()
3016        } else {
3017            field
3018                .homogeneous_fraction(&numerator, &denominator)
3019                .map_err(curve_serialization_error)?
3020        };
3021        let canonical = rebuilt
3022            .homogeneous_representation()
3023            .map_err(curve_serialization_error)?;
3024        if canonical != (numerator, denominator) {
3025            return Err(NoetherError::Serialization(
3026                "serialized curve-function fraction is not canonical".to_owned(),
3027            ));
3028        }
3029        Ok(rebuilt)
3030    }
3031}
3032
3033impl CurveFunction<RationalField> {
3034    /// Complete principal Weil divisor over `Q`, including non-rational closed points.
3035    pub fn principal_divisor(&self) -> Result<WeilDivisor<RationalField>, CurveError> {
3036        principal_divisor_with_factorization(self, |polynomial: &Polynomial<RationalField>| {
3037            polynomial.factor()
3038        })
3039    }
3040
3041    /// Independently recompute and compare a proposed principal divisor.
3042    pub fn verify_principal_divisor(
3043        &self,
3044        divisor: &WeilDivisor<RationalField>,
3045    ) -> Result<(), VerificationError> {
3046        if &self
3047            .principal_divisor()
3048            .map_err(|_| VerificationError::InvalidWitness {
3049                claim: "principal divisor is computable over Q",
3050            })?
3051            == divisor
3052        {
3053            Ok(())
3054        } else {
3055            Err(VerificationError::InvalidWitness {
3056                claim: "proposed divisor equals the function's complete zero-minus-pole divisor",
3057            })
3058        }
3059    }
3060}
3061
3062impl CurveFunction<FiniteField> {
3063    /// Complete principal Weil divisor over a finite field, with residue-field places retained.
3064    pub fn principal_divisor(&self) -> Result<WeilDivisor<FiniteField>, CurveError> {
3065        principal_divisor_with_factorization(self, |polynomial: &Polynomial<FiniteField>| {
3066            polynomial.factor()
3067        })
3068    }
3069
3070    /// Independently recompute and compare a proposed principal divisor.
3071    pub fn verify_principal_divisor(
3072        &self,
3073        divisor: &WeilDivisor<FiniteField>,
3074    ) -> Result<(), VerificationError> {
3075        if &self
3076            .principal_divisor()
3077            .map_err(|_| VerificationError::InvalidWitness {
3078                claim: "principal divisor is computable over a finite field",
3079            })?
3080            == divisor
3081        {
3082            Ok(())
3083        } else {
3084            Err(VerificationError::InvalidWitness {
3085                claim: "proposed divisor equals the function's complete zero-minus-pole divisor",
3086            })
3087        }
3088    }
3089}
3090
3091impl WeilDivisor<RationalField> {
3092    /// Scheme-theoretic pullback along a finite checked curve morphism over `Q`.
3093    pub fn pullback(&self, morphism: &CurveMorphism<RationalField>) -> Result<Self, CurveError> {
3094        pullback_divisor_with_factorization(self, morphism, |polynomial| polynomial.factor())
3095    }
3096}
3097
3098impl WeilDivisor<FiniteField> {
3099    /// Scheme-theoretic pullback along a finite checked curve morphism over a finite field.
3100    pub fn pullback(&self, morphism: &CurveMorphism<FiniteField>) -> Result<Self, CurveError> {
3101        pullback_divisor_with_factorization(self, morphism, |polynomial| polynomial.factor())
3102    }
3103}
3104
3105fn pullback_divisor_with_factorization<R, F>(
3106    divisor: &WeilDivisor<R>,
3107    morphism: &CurveMorphism<R>,
3108    factor: F,
3109) -> Result<WeilDivisor<R>, CurveError>
3110where
3111    R: Field,
3112    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>
3113        + Copy,
3114{
3115    if divisor.curve() != morphism.target() {
3116        return Err(ContextMismatchError {
3117            expected: Curve::<R>::TYPE_ID.to_owned(),
3118            actual: "divisor is not on the morphism target".to_owned(),
3119        }
3120        .into());
3121    }
3122    let source_ring = morphism.source.equation.ring();
3123    let mut result = WeilDivisor::zero(morphism.source.clone());
3124    for (point, coefficient) in &divisor.terms {
3125        let mut generators = morphism
3126            .source
3127            .scheme
3128            .defining_ideal()
3129            .generators()
3130            .to_vec();
3131        for generator in point.prime_ideal.ideal().generators() {
3132            generators.push(evaluate_polynomial_at_map(
3133                generator,
3134                source_ring,
3135                &morphism.coordinate_images,
3136            )?);
3137        }
3138        let inverse_image = source_ring.ideal(generators)?;
3139        let effective = match inverse_image.dimension()? {
3140            -1 => WeilDivisor::zero(morphism.source.clone()),
3141            1 => {
3142                let expected_degree = inverse_image.degree()?;
3143                projective_ideal_divisor(
3144                    morphism.source(),
3145                    &inverse_image,
3146                    &expected_degree,
3147                    factor,
3148                )?
3149            }
3150            _ => {
3151                return Err(CurveError::InvalidWitness(
3152                    "divisor pullback is defined only for zero-dimensional point inverse images",
3153                ));
3154            }
3155        };
3156        result = result.checked_add(&effective.scale(coefficient))?;
3157    }
3158    Ok(result)
3159}
3160
3161fn principal_divisor_with_factorization<R, F>(
3162    function: &CurveFunction<R>,
3163    factor: F,
3164) -> Result<WeilDivisor<R>, CurveError>
3165where
3166    R: Field,
3167    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>
3168        + Copy,
3169{
3170    if function.is_zero() {
3171        return Err(CurveError::InvalidWitness(
3172            "the zero function has no principal divisor",
3173        ));
3174    }
3175    let (numerator, denominator) = canonical_projective_fraction(function)?;
3176    let zeros = projective_section_divisor(function.field.curve(), &numerator, factor)?;
3177    let poles = projective_section_divisor(function.field.curve(), &denominator, factor)?;
3178    let divisor = zeros.checked_sub(&poles)?;
3179    if !divisor.degree().is_zero() {
3180        return Err(CurveError::InvalidWitness(
3181            "a principal divisor has degree zero",
3182        ));
3183    }
3184    Ok(divisor)
3185}
3186
3187fn canonical_projective_fraction<R: Field>(
3188    function: &CurveFunction<R>,
3189) -> Result<(Polynomial<R>, Polynomial<R>), CurveError> {
3190    let field = function.field();
3191    let univariate = field.rational_field.polynomial_ring();
3192    let mut common_denominator = univariate.one();
3193    for (_, coefficient) in function.representative.terms() {
3194        let gcd = common_denominator.gcd(coefficient.denominator())?;
3195        let new_factor = coefficient.denominator().exact_div(&gcd)?;
3196        common_denominator = (&common_denominator * &new_factor)?;
3197    }
3198    let mut numerator_terms = Vec::new();
3199    for (y_exponent, coefficient) in function.representative.terms() {
3200        let scale = common_denominator.exact_div(coefficient.denominator())?;
3201        let scaled_numerator = (coefficient.numerator() * &scale)?;
3202        for (x_exponent, scalar) in scaled_numerator.terms() {
3203            numerator_terms.push((x_exponent[0].clone(), y_exponent[0].clone(), scalar.clone()));
3204        }
3205    }
3206    let denominator_terms = common_denominator
3207        .terms()
3208        .map(|(x_exponent, scalar)| (x_exponent[0].clone(), Integer::zero(), scalar.clone()))
3209        .collect::<Vec<_>>();
3210    let degree = numerator_terms
3211        .iter()
3212        .chain(&denominator_terms)
3213        .map(|(x, y, _)| x + y)
3214        .max()
3215        .ok_or(CurveError::InvalidWitness(
3216            "a nonzero function has a nonzero projective fraction",
3217        ))?;
3218    let numerator = homogenize_xy_terms(field, numerator_terms, &degree)?;
3219    let denominator = homogenize_xy_terms(field, denominator_terms, &degree)?;
3220    if numerator.is_zero() || denominator.is_zero() {
3221        return Err(CurveError::InvalidWitness(
3222            "a nonzero curve function has nonzero numerator and denominator sections",
3223        ));
3224    }
3225    Ok((numerator, denominator))
3226}
3227
3228fn homogenize_xy_terms<R: Field>(
3229    field: &CurveFunctionField<R>,
3230    terms: Vec<(Integer, Integer, R::Element)>,
3231    degree: &Integer,
3232) -> Result<Polynomial<R>, CurveError> {
3233    let ring = field.curve.equation.ring();
3234    ring.from_terms(terms.into_iter().map(|(x, y, coefficient)| {
3235        let used = &x + &y;
3236        let patch = degree - &used;
3237        let mut exponent = vec![Integer::zero(); 3];
3238        exponent[field.transcendental_coordinate] = x;
3239        exponent[field.algebraic_coordinate] = y;
3240        exponent[field.patch_coordinate] = patch;
3241        (exponent, coefficient)
3242    }))
3243    .map_err(Into::into)
3244}
3245
3246fn projective_section_divisor<R, F>(
3247    curve: &Curve<R>,
3248    section: &Polynomial<R>,
3249    factor: F,
3250) -> Result<WeilDivisor<R>, CurveError>
3251where
3252    R: Field,
3253    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>
3254        + Copy,
3255{
3256    let degree = section
3257        .homogeneous_degree()?
3258        .ok_or(CurveError::InvalidWitness(
3259            "a projective section is homogeneous",
3260        ))?;
3261    if degree.is_zero() {
3262        return Ok(WeilDivisor::zero(curve.clone()));
3263    }
3264    let curve_degree = curve
3265        .equation
3266        .homogeneous_degree()?
3267        .ok_or(CurveError::InvalidWitness(
3268            "a plane curve equation is homogeneous",
3269        ))?;
3270    let expected_degree = &curve_degree * &degree;
3271    let projective_ring = curve.equation.ring();
3272    let intersection = curve
3273        .scheme
3274        .defining_ideal()
3275        .sum(&projective_ring.ideal([section.clone()])?)?;
3276    projective_ideal_divisor(curve, &intersection, &expected_degree, factor)
3277}
3278
3279fn projective_ideal_divisor<R, F>(
3280    curve: &Curve<R>,
3281    ideal: &super::Ideal<PolynomialRing<R>>,
3282    expected_degree: &Integer,
3283    factor: F,
3284) -> Result<WeilDivisor<R>, CurveError>
3285where
3286    R: Field,
3287    F: Fn(&Polynomial<R>) -> Result<PolynomialFactorization<R>, PolynomialFactorizationError>
3288        + Copy,
3289{
3290    let projective_ring = curve.equation.ring();
3291    if ideal.ring() != projective_ring {
3292        return Err(ContextMismatchError {
3293            expected: projective_ring.to_string(),
3294            actual: ideal.ring().to_string(),
3295        }
3296        .into());
3297    }
3298    let mut terms = Vec::new();
3299    for chart in 0..3 {
3300        let affine_names = projective_ring
3301            .variables()
3302            .iter()
3303            .enumerate()
3304            .filter(|(index, _)| *index != chart)
3305            .map(|(_, name)| name.clone());
3306        let affine_ring = PolynomialRing::new(
3307            curve.base().clone(),
3308            affine_names,
3309            MonomialOrder::GradedReverseLexicographic,
3310        )?;
3311        let generators = ideal
3312            .generators()
3313            .iter()
3314            .map(|generator| dehomogenize_to_chart(generator, &affine_ring, chart))
3315            .collect::<Result<Vec<_>, _>>()?;
3316        let intersection = affine_ring.ideal(generators)?;
3317        match intersection.dimension()? {
3318            -1 => continue,
3319            0 => {}
3320            _ => {
3321                return Err(CurveError::InvalidWitness(
3322                    "a nonzero section cuts a zero-dimensional divisor on an integral curve",
3323                ));
3324            }
3325        }
3326        let representation = intersection.rational_univariate_representation()?;
3327        let factorization = factor(representation.equation())?;
3328        for (parameter_factor, multiplicity) in factorization.factors() {
3329            let affine_prime = affine_prime_from_rur_factor(&representation, parameter_factor)?;
3330            let projective_prime =
3331                projective_closure_of_affine_prime(&affine_prime, projective_ring, chart)?;
3332            let belongs_to_stratum = (0..chart).try_fold(true, |belongs, earlier| {
3333                projective_prime
3334                    .contains(&projective_ring.generator(earlier)?)
3335                    .map(|contains| belongs && contains)
3336                    .map_err(CurveError::from)
3337            })?;
3338            if !belongs_to_stratum {
3339                continue;
3340            }
3341            let point = CurveClosedPoint::from_prime_ideal(
3342                curve.clone(),
3343                PrimeIdeal::from_verified_parts(
3344                    projective_prime,
3345                    b"irreducible RUR factor in a projective chart".to_vec(),
3346                ),
3347            )?;
3348            terms.push((point, Integer::from(*multiplicity)));
3349        }
3350    }
3351    let divisor = WeilDivisor::new(curve.clone(), terms)?;
3352    if &divisor.degree() != expected_degree {
3353        return Err(CurveError::InvalidWitness(
3354            "projective chart decomposition has the complete Bezout degree",
3355        ));
3356    }
3357    Ok(divisor)
3358}
3359
3360fn affine_prime_from_rur_factor<R: Field>(
3361    representation: &super::RationalUnivariateRepresentation<R>,
3362    factor: &Polynomial<R>,
3363) -> Result<super::Ideal<PolynomialRing<R>>, CurveError> {
3364    let ring = representation.source().ring();
3365    let mut linear_form = ring.zero();
3366    for (variable, coefficient) in representation.linear_form().iter().enumerate() {
3367        linear_form = (&linear_form + &ring.generator(variable)?.scalar_mul(coefficient)?)?;
3368    }
3369    let factor_at_parameter = substitute_univariate(factor, &linear_form)?;
3370    let denominator = substitute_univariate(representation.denominator(), &linear_form)?;
3371    let mut generators = vec![factor_at_parameter];
3372    for (variable, numerator) in representation.coordinate_numerators().iter().enumerate() {
3373        let numerator = substitute_univariate(numerator, &linear_form)?;
3374        let left = (&denominator * &ring.generator(variable)?)?;
3375        generators.push((&left - &numerator)?);
3376    }
3377    let prime = ring.ideal(generators)?;
3378    let factor_degree = factor.degree(Some(0))?.ok_or(CurveError::InvalidWitness(
3379        "an irreducible RUR factor is nonzero",
3380    ))?;
3381    if prime.dimension()? != 0 || prime.degree()? != factor_degree {
3382        return Err(CurveError::InvalidWitness(
3383            "an irreducible RUR factor defines a residue field of the claimed degree",
3384        ));
3385    }
3386    for generator in representation.source().generators() {
3387        if !prime.contains(generator)? {
3388            return Err(CurveError::InvalidWitness(
3389                "an RUR-factor prime contains the source intersection ideal",
3390            ));
3391        }
3392    }
3393    Ok(prime)
3394}
3395
3396fn substitute_univariate<R: Field>(
3397    polynomial: &Polynomial<R>,
3398    image: &Polynomial<R>,
3399) -> Result<Polynomial<R>, CurveError> {
3400    let ring = image.ring();
3401    let mut result = ring.zero();
3402    for (exponent, coefficient) in polynomial.terms() {
3403        let power = image.pow_integer(&exponent[0])?;
3404        result = (&result + &power.scalar_mul(coefficient)?)?;
3405    }
3406    Ok(result)
3407}
3408
3409fn dehomogenize_to_chart<R: Field>(
3410    polynomial: &Polynomial<R>,
3411    target: &PolynomialRing<R>,
3412    chart: usize,
3413) -> Result<Polynomial<R>, CurveError> {
3414    target
3415        .from_terms(polynomial.terms().map(|(exponent, coefficient)| {
3416            let mut exponent = exponent.to_vec();
3417            exponent.remove(chart);
3418            (exponent, coefficient.clone())
3419        }))
3420        .map_err(Into::into)
3421}
3422
3423fn projective_closure_of_affine_prime<R: Field>(
3424    affine_prime: &super::Ideal<PolynomialRing<R>>,
3425    projective_ring: &PolynomialRing<R>,
3426    chart: usize,
3427) -> Result<super::Ideal<PolynomialRing<R>>, CurveError> {
3428    let generators = affine_prime
3429        .generators()
3430        .iter()
3431        .map(|generator| homogenize_affine_polynomial(generator, projective_ring, chart))
3432        .collect::<Result<Vec<_>, _>>()?;
3433    let closure = projective_ring.ideal(generators)?;
3434    let patch_ideal = projective_ring.ideal([projective_ring.generator(chart)?])?;
3435    closure.saturate(&patch_ideal).map_err(Into::into)
3436}
3437
3438fn homogenize_affine_polynomial<R: Field>(
3439    polynomial: &Polynomial<R>,
3440    target: &PolynomialRing<R>,
3441    chart: usize,
3442) -> Result<Polynomial<R>, CurveError> {
3443    let degree = polynomial.degree(None)?.unwrap_or_else(Integer::zero);
3444    target
3445        .from_terms(polynomial.terms().map(|(exponent, coefficient)| {
3446            let used = exponent
3447                .iter()
3448                .fold(Integer::zero(), |sum, value| sum + value);
3449            let mut homogeneous = exponent.to_vec();
3450            homogeneous.insert(chart, &degree - &used);
3451            (homogeneous, coefficient.clone())
3452        }))
3453        .map_err(Into::into)
3454}
3455
3456#[derive(Clone)]
3457struct LaurentGerm<R: Field> {
3458    field: R,
3459    coefficients: std::collections::BTreeMap<i64, R::Element>,
3460    known_through: i64,
3461}
3462
3463impl<R: Field> LaurentGerm<R> {
3464    fn zero(field: R, known_through: i64) -> Self {
3465        Self {
3466            field,
3467            coefficients: std::collections::BTreeMap::new(),
3468            known_through,
3469        }
3470    }
3471
3472    fn constant(field: R, value: R::Element, known_through: i64) -> Self {
3473        let mut result = Self::zero(field, known_through);
3474        if value != result.field.zero() {
3475            result.coefficients.insert(0, value);
3476        }
3477        result
3478    }
3479
3480    fn parameter(field: R, known_through: i64) -> Self {
3481        let mut result = Self::zero(field.clone(), known_through);
3482        if known_through >= 1 {
3483            result.coefficients.insert(1, field.one());
3484        }
3485        result
3486    }
3487
3488    fn valuation(&self) -> Option<i64> {
3489        self.coefficients.keys().next().copied()
3490    }
3491
3492    fn coefficient(&self, exponent: i64) -> R::Element {
3493        self.coefficients
3494            .get(&exponent)
3495            .cloned()
3496            .unwrap_or_else(|| self.field.zero())
3497    }
3498
3499    fn set_coefficient(&mut self, exponent: i64, value: R::Element) {
3500        if exponent > self.known_through {
3501            return;
3502        }
3503        if value == self.field.zero() {
3504            self.coefficients.remove(&exponent);
3505        } else {
3506            self.coefficients.insert(exponent, value);
3507        }
3508    }
3509
3510    fn add(&self, other: &Self) -> Result<Self, CurveError> {
3511        self.require(other)?;
3512        let known_through = self.known_through.min(other.known_through);
3513        let mut result = Self::zero(self.field.clone(), known_through);
3514        for exponent in self
3515            .coefficients
3516            .keys()
3517            .chain(other.coefficients.keys())
3518            .copied()
3519            .filter(|exponent| *exponent <= known_through)
3520            .collect::<std::collections::BTreeSet<_>>()
3521        {
3522            result.set_coefficient(
3523                exponent,
3524                self.field
3525                    .add(&self.coefficient(exponent), &other.coefficient(exponent))?,
3526            );
3527        }
3528        Ok(result)
3529    }
3530
3531    fn mul(&self, other: &Self) -> Result<Self, CurveError> {
3532        self.require(other)?;
3533        let known_through = match (self.valuation(), other.valuation()) {
3534            (Some(left), Some(right)) => {
3535                left + right + (self.known_through - left).min(other.known_through - right)
3536            }
3537            (None, Some(right)) => self.known_through + right,
3538            (Some(left), None) => other.known_through + left,
3539            (None, None) => self.known_through + other.known_through + 1,
3540        };
3541        let mut result = Self::zero(self.field.clone(), known_through);
3542        for (left_exponent, left_coefficient) in &self.coefficients {
3543            for (right_exponent, right_coefficient) in &other.coefficients {
3544                let exponent = left_exponent + right_exponent;
3545                if exponent > known_through {
3546                    continue;
3547                }
3548                let product = self.field.mul(left_coefficient, right_coefficient)?;
3549                let coefficient = self.field.add(&result.coefficient(exponent), &product)?;
3550                result.set_coefficient(exponent, coefficient);
3551            }
3552        }
3553        Ok(result)
3554    }
3555
3556    fn pow(&self, mut exponent: usize) -> Result<Self, CurveError> {
3557        let mut result = Self::constant(self.field.clone(), self.field.one(), self.known_through);
3558        let mut base = self.clone();
3559        while exponent > 0 {
3560            if exponent & 1 == 1 {
3561                result = result.mul(&base)?;
3562            }
3563            exponent >>= 1;
3564            if exponent > 0 {
3565                base = base.mul(&base)?;
3566            }
3567        }
3568        Ok(result)
3569    }
3570
3571    fn inverse(&self) -> Result<Self, CurveError> {
3572        let valuation = self.valuation().ok_or(CurveError::LocalExpansionLimit {
3573            limit: usize::try_from(self.known_through.max(0)).unwrap_or(usize::MAX),
3574        })?;
3575        let leading = self.coefficient(valuation);
3576        let leading_inverse = self
3577            .field
3578            .inverse(&leading)
3579            .map_err(|_| CurveError::InvalidWitness("a germ leading coefficient is nonzero"))?;
3580        let relative_precision = self.known_through - valuation;
3581        let known_through = -valuation + relative_precision;
3582        let mut normalized_inverse = vec![self.field.zero(); (relative_precision + 1) as usize];
3583        normalized_inverse[0] = leading_inverse.clone();
3584        for degree in 1..=relative_precision as usize {
3585            let mut sum = self.field.zero();
3586            for index in 1..=degree {
3587                let coefficient = self.coefficient(
3588                    valuation
3589                        + i64::try_from(index)
3590                            .map_err(|_| CurveError::LocalExpansionLimit { limit: degree })?,
3591                );
3592                let product = self
3593                    .field
3594                    .mul(&coefficient, &normalized_inverse[degree - index])?;
3595                sum = self.field.add(&sum, &product)?;
3596            }
3597            normalized_inverse[degree] =
3598                self.field.neg(&self.field.mul(&leading_inverse, &sum)?)?;
3599        }
3600        let mut result = Self::zero(self.field.clone(), known_through);
3601        for (degree, coefficient) in normalized_inverse.into_iter().enumerate() {
3602            let exponent = -valuation
3603                + i64::try_from(degree)
3604                    .map_err(|_| CurveError::LocalExpansionLimit { limit: degree })?;
3605            result.set_coefficient(exponent, coefficient);
3606        }
3607        Ok(result)
3608    }
3609
3610    fn checked_div(&self, other: &Self) -> Result<Self, CurveError> {
3611        self.mul(&other.inverse()?)
3612    }
3613
3614    fn require(&self, other: &Self) -> Result<(), CurveError> {
3615        if self.field == other.field {
3616            Ok(())
3617        } else {
3618            Err(ContextMismatchError {
3619                expected: R::TYPE_ID.to_owned(),
3620                actual: "local germs use unequal coefficient contexts".to_owned(),
3621            }
3622            .into())
3623        }
3624    }
3625}
3626
3627fn local_work_precision<R: Field>(
3628    function: &CurveFunction<R>,
3629    limit: usize,
3630) -> Result<usize, CurveError> {
3631    let mut degree_allowance = function.field.extension_degree.saturating_mul(8);
3632    for (_, coefficient) in function.representative.terms() {
3633        for polynomial in [coefficient.numerator(), coefficient.denominator()] {
3634            if let Some(degree) = polynomial.degree(Some(0))? {
3635                degree_allowance = degree_allowance.saturating_add(integer_to_usize(&degree)?);
3636            }
3637        }
3638    }
3639    limit
3640        .checked_mul(4)
3641        .and_then(|value| value.checked_add(degree_allowance))
3642        .and_then(|value| value.checked_add(32))
3643        .filter(|value| *value <= 1_000_000)
3644        .ok_or(CurveError::LocalExpansionLimit { limit })
3645}
3646
3647fn curve_function_germ<R: Field>(
3648    function: &CurveFunction<R>,
3649    point: &CurvePoint<R>,
3650    precision: usize,
3651) -> Result<LaurentGerm<R>, CurveError> {
3652    let coordinate_germs = projective_coordinate_germs(point, precision)?;
3653    curve_function_germ_from_coordinates(function, &coordinate_germs)
3654}
3655
3656fn curve_function_germ_from_coordinates<R: Field>(
3657    function: &CurveFunction<R>,
3658    coordinate_germs: &[LaurentGerm<R>; 3],
3659) -> Result<LaurentGerm<R>, CurveError> {
3660    let field = function.field.curve.base().clone();
3661    let x = coordinate_germs[function.field.transcendental_coordinate]
3662        .checked_div(&coordinate_germs[function.field.patch_coordinate])?;
3663    let y = coordinate_germs[function.field.algebraic_coordinate]
3664        .checked_div(&coordinate_germs[function.field.patch_coordinate])?;
3665    let known_through = coordinate_germs
3666        .iter()
3667        .map(|coordinate| coordinate.known_through)
3668        .min()
3669        .ok_or(CurveError::InvalidWitness(
3670            "a plane curve has three projective coordinate germs",
3671        ))?;
3672    let mut result = LaurentGerm::zero(field.clone(), known_through);
3673    for (exponent, coefficient) in function.representative.terms() {
3674        let y_power = integer_to_usize(&exponent[0])?;
3675        let numerator = evaluate_univariate_germ(coefficient.numerator(), &x)?;
3676        let denominator = evaluate_univariate_germ(coefficient.denominator(), &x)?;
3677        let coefficient = numerator.checked_div(&denominator)?;
3678        let term = coefficient.mul(&y.pow(y_power)?)?;
3679        result = result.add(&term)?;
3680    }
3681    Ok(result)
3682}
3683
3684fn projective_coordinate_germs<R: Field>(
3685    point: &CurvePoint<R>,
3686    precision: usize,
3687) -> Result<[LaurentGerm<R>; 3], CurveError> {
3688    let field = point.curve.base().clone();
3689    let high = i64::try_from(precision)
3690        .map_err(|_| CurveError::LocalExpansionLimit { limit: precision })?;
3691    let zero = field.zero();
3692    let chart = point
3693        .coordinates()
3694        .iter()
3695        .position(|coordinate| coordinate != &zero)
3696        .ok_or(CurveError::InvalidWitness(
3697            "a checked projective point has a nonzero chart coordinate",
3698        ))?;
3699    let chart_inverse = field
3700        .inverse(&point.coordinates()[chart])
3701        .map_err(|_| CurveError::InvalidWitness("the chart coordinate is nonzero"))?;
3702    let normalized = point
3703        .coordinates()
3704        .iter()
3705        .map(|coordinate| field.mul(coordinate, &chart_inverse).map_err(Into::into))
3706        .collect::<Result<Vec<_>, CurveError>>()?;
3707    let local = (0..3).filter(|index| *index != chart).collect::<Vec<_>>();
3708    let derivatives = (0..3)
3709        .map(|variable| {
3710            point
3711                .curve
3712                .equation
3713                .derivative(variable)?
3714                .evaluate(&normalized)
3715        })
3716        .collect::<Result<Vec<_>, PolynomialError>>()?;
3717    let (parameter, dependent) = if derivatives[local[1]] != zero {
3718        (local[0], local[1])
3719    } else if derivatives[local[0]] != zero {
3720        (local[1], local[0])
3721    } else {
3722        return Err(CurveError::SingularCurve);
3723    };
3724    let derivative_inverse = field
3725        .inverse(&derivatives[dependent])
3726        .map_err(|_| CurveError::SingularCurve)?;
3727
3728    let constant = |value: R::Element| LaurentGerm::constant(field.clone(), value, high);
3729    let mut coordinates = [
3730        constant(normalized[0].clone()),
3731        constant(normalized[1].clone()),
3732        constant(normalized[2].clone()),
3733    ];
3734    coordinates[chart] = constant(field.one());
3735    coordinates[parameter] =
3736        coordinates[parameter].add(&LaurentGerm::parameter(field.clone(), high))?;
3737    for order in 1..=precision {
3738        let residual = evaluate_homogeneous_germ(&point.curve.equation, &coordinates)?;
3739        let exponent = i64::try_from(order)
3740            .map_err(|_| CurveError::LocalExpansionLimit { limit: precision })?;
3741        let correction =
3742            field.neg(&field.mul(&residual.coefficient(exponent), &derivative_inverse)?)?;
3743        coordinates[dependent].set_coefficient(exponent, correction);
3744    }
3745    let residual = evaluate_homogeneous_germ(&point.curve.equation, &coordinates)?;
3746    if residual
3747        .coefficients
3748        .range(..=high)
3749        .any(|(_, coefficient)| coefficient != &zero)
3750    {
3751        return Err(CurveError::InvalidWitness(
3752            "implicit local expansion satisfies the curve equation",
3753        ));
3754    }
3755    Ok(coordinates)
3756}
3757
3758fn evaluate_homogeneous_germ<R: Field>(
3759    polynomial: &Polynomial<R>,
3760    coordinates: &[LaurentGerm<R>; 3],
3761) -> Result<LaurentGerm<R>, CurveError> {
3762    let field = polynomial.ring().coefficient_ring().clone();
3763    let high = coordinates
3764        .iter()
3765        .map(|coordinate| coordinate.known_through)
3766        .min()
3767        .unwrap_or(0);
3768    let mut result = LaurentGerm::zero(field.clone(), high);
3769    for (exponent, coefficient) in polynomial.terms() {
3770        let mut term = LaurentGerm::constant(field.clone(), coefficient.clone(), high);
3771        for (coordinate, exponent) in coordinates.iter().zip(exponent) {
3772            term = term.mul(&coordinate.pow(integer_to_usize(exponent)?)?)?;
3773        }
3774        result = result.add(&term)?;
3775    }
3776    Ok(result)
3777}
3778
3779fn evaluate_univariate_germ<R: Field>(
3780    polynomial: &Polynomial<R>,
3781    value: &LaurentGerm<R>,
3782) -> Result<LaurentGerm<R>, CurveError> {
3783    let mut result = LaurentGerm::zero(value.field.clone(), value.known_through);
3784    for (exponent, coefficient) in polynomial.terms() {
3785        let term = LaurentGerm::constant(
3786            value.field.clone(),
3787            coefficient.clone(),
3788            value.known_through,
3789        )
3790        .mul(&value.pow(integer_to_usize(&exponent[0])?)?)?;
3791        result = result.add(&term)?;
3792    }
3793    Ok(result)
3794}
3795
3796impl<R: Field> fmt::Debug for CurveFunction<R> {
3797    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3798        formatter
3799            .debug_struct("CurveFunction")
3800            .field("base", &R::TYPE_ID)
3801            .field("terms", &self.representative.nonzero_term_count())
3802            .finish()
3803    }
3804}
3805
3806impl<R: Field> fmt::Display for CurveFunction<R> {
3807    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3808        write!(
3809            formatter,
3810            "CurveFunction<{}>(terms={})",
3811            R::TYPE_ID,
3812            self.representative.nonzero_term_count()
3813        )
3814    }
3815}
3816
3817impl<R: Field> SemanticType for CurveFunction<R> {
3818    const TYPE_ID: &'static str = "noether/commalg/CurveFunction@1";
3819
3820    fn canonical_bytes(&self) -> Result<Vec<u8>, NoetherError> {
3821        let mut output = self.field.canonical_bytes()?;
3822        append_component(&mut output, &self.representative.canonical_bytes()?)?;
3823        Ok(output)
3824    }
3825
3826    fn describe() -> &'static TypeDescription {
3827        &CURVE_FUNCTION_DESCRIPTION
3828    }
3829}
3830
3831impl<R: Field> Add<&CurveFunction<R>> for &CurveFunction<R> {
3832    type Output = Result<CurveFunction<R>, CurveError>;
3833
3834    fn add(self, rhs: &CurveFunction<R>) -> Self::Output {
3835        self.field.checked_add(self, rhs)
3836    }
3837}
3838
3839impl<R: Field> Sub<&CurveFunction<R>> for &CurveFunction<R> {
3840    type Output = Result<CurveFunction<R>, CurveError>;
3841
3842    fn sub(self, rhs: &CurveFunction<R>) -> Self::Output {
3843        self.field.checked_sub(self, rhs)
3844    }
3845}
3846
3847impl<R: Field> Mul<&CurveFunction<R>> for &CurveFunction<R> {
3848    type Output = Result<CurveFunction<R>, CurveError>;
3849
3850    fn mul(self, rhs: &CurveFunction<R>) -> Self::Output {
3851        self.field.checked_mul(self, rhs)
3852    }
3853}
3854
3855impl<R: Field> Div<&CurveFunction<R>> for &CurveFunction<R> {
3856    type Output = Result<CurveFunction<R>, CurveError>;
3857
3858    fn div(self, rhs: &CurveFunction<R>) -> Self::Output {
3859        self.checked_div(rhs)
3860    }
3861}
3862
3863impl<R: Field> Neg for &CurveFunction<R> {
3864    type Output = Result<CurveFunction<R>, CurveError>;
3865
3866    fn neg(self) -> Self::Output {
3867        self.field.checked_neg(self)
3868    }
3869}
3870
3871type ExtendedGcdCoefficientResult<R> = Result<
3872    (
3873        Polynomial<RationalFunctionField<R>>,
3874        Polynomial<RationalFunctionField<R>>,
3875    ),
3876    CurveError,
3877>;
3878
3879fn extended_gcd_coefficient<R: Field>(
3880    modulus: &Polynomial<RationalFunctionField<R>>,
3881    value: &Polynomial<RationalFunctionField<R>>,
3882) -> ExtendedGcdCoefficientResult<R> {
3883    let ring = modulus.ring();
3884    let mut old_remainder = modulus.clone();
3885    let mut remainder = value.clone();
3886    let mut old_coefficient = ring.zero();
3887    let mut coefficient = ring.one();
3888    while !remainder.is_zero() {
3889        let (quotient, next_remainder) = old_remainder.div_rem(&remainder)?;
3890        let product = (&quotient * &coefficient)?;
3891        let next_coefficient = (&old_coefficient - &product)?;
3892        old_remainder = remainder;
3893        remainder = next_remainder;
3894        old_coefficient = coefficient;
3895        coefficient = next_coefficient;
3896    }
3897    Ok((old_remainder, old_coefficient))
3898}
3899
3900fn coordinate_presentations() -> Vec<(usize, usize, usize)> {
3901    let mut presentations = Vec::with_capacity(6);
3902    for patch in (0..3).rev() {
3903        let remaining = (0..3).filter(|index| *index != patch).collect::<Vec<_>>();
3904        presentations.push((remaining[0], remaining[1], patch));
3905        presentations.push((remaining[1], remaining[0], patch));
3906    }
3907    presentations
3908}
3909
3910fn integer_to_usize(value: &crate::Integer) -> Result<usize, CurveError> {
3911    value
3912        .to_u64_checked()
3913        .and_then(|value| usize::try_from(value).ok())
3914        .ok_or(CurveError::NoFunctionFieldPresentation)
3915}
3916
3917fn integer_to_u64(value: &crate::Integer) -> Result<u64, CurveError> {
3918    value.to_u64_checked().ok_or(CurveError::ResourceLimit {
3919        resource: "polynomial or section exponent",
3920        limit: usize::MAX,
3921    })
3922}
3923
3924fn small_exponents(values: [u64; 3]) -> Vec<crate::Integer> {
3925    values.into_iter().map(crate::Integer::from).collect()
3926}
3927
3928fn natural_multiple<R: Field>(
3929    field: &R,
3930    value: &R::Element,
3931    mut factor: u64,
3932) -> Result<R::Element, CurveError> {
3933    let mut result = field.zero();
3934    let mut summand = value.clone();
3935    while factor > 0 {
3936        if factor & 1 == 1 {
3937            result = field.add(&result, &summand)?;
3938        }
3939        factor >>= 1;
3940        if factor > 0 {
3941            summand = field.add(&summand, &summand)?;
3942        }
3943    }
3944    Ok(result)
3945}
3946
3947fn weierstrass_discriminant<R: Field>(
3948    field: &R,
3949    coefficients: &[R::Element; 5],
3950) -> Result<R::Element, CurveError> {
3951    let [a1, a2, a3, a4, a6] = coefficients;
3952    let a1_squared = field.mul(a1, a1)?;
3953    let a3_squared = field.mul(a3, a3)?;
3954    let a4_squared = field.mul(a4, a4)?;
3955    let b2 = field.add(&a1_squared, &natural_multiple(field, a2, 4)?)?;
3956    let b4 = field.add(&field.mul(a1, a3)?, &natural_multiple(field, a4, 2)?)?;
3957    let b6 = field.add(&a3_squared, &natural_multiple(field, a6, 4)?)?;
3958    let b8 = {
3959        let first = field.mul(&a1_squared, a6)?;
3960        let second = natural_multiple(field, &field.mul(a2, a6)?, 4)?;
3961        let third = field.mul(&field.mul(a1, a3)?, a4)?;
3962        let fourth = field.mul(a2, &a3_squared)?;
3963        let value = field.add(&first, &second)?;
3964        let value = field.sub(&value, &third)?;
3965        let value = field.add(&value, &fourth)?;
3966        field.sub(&value, &a4_squared)?
3967    };
3968    let term1 = field.neg(&field.mul(&field.mul(&b2, &b2)?, &b8)?)?;
3969    let b4_cubed = field.mul(&field.mul(&b4, &b4)?, &b4)?;
3970    let term2 = field.neg(&natural_multiple(field, &b4_cubed, 8)?)?;
3971    let term3 = field.neg(&natural_multiple(field, &field.mul(&b6, &b6)?, 27)?)?;
3972    let term4 = natural_multiple(field, &field.mul(&field.mul(&b2, &b4)?, &b6)?, 9)?;
3973    let value = field.add(&term1, &term2)?;
3974    let value = field.add(&value, &term3)?;
3975    field.add(&value, &term4).map_err(Into::into)
3976}
3977
3978fn curve_context_error<R: Field>(error: CurveError) -> ContextMismatchError {
3979    match error {
3980        CurveError::ContextMismatch(error) => error,
3981        error => ContextMismatchError {
3982            expected: CurveFunctionField::<R>::TYPE_ID.to_owned(),
3983            actual: format!("curve-function arithmetic invariant failed: {error}"),
3984        },
3985    }
3986}
3987
3988fn append_component(output: &mut Vec<u8>, component: &[u8]) -> Result<(), NoetherError> {
3989    let length = u64::try_from(component.len())
3990        .map_err(|_| NoetherError::Serialization("curve component is too large".to_owned()))?;
3991    output.extend_from_slice(&length.to_be_bytes());
3992    output.extend_from_slice(component);
3993    Ok(())
3994}
3995
3996fn serialization_error(error: impl fmt::Display) -> NoetherError {
3997    NoetherError::Serialization(error.to_string())
3998}
3999
4000fn curve_serialization_error(error: CurveError) -> NoetherError {
4001    NoetherError::Serialization(error.to_string())
4002}
4003
4004fn encode_typed_json<const N: usize>(
4005    type_id: &str,
4006    fields: [(&str, serde_json::Value); N],
4007) -> Result<String, NoetherError> {
4008    let mut object = serde_json::Map::new();
4009    object.insert(
4010        "type".to_owned(),
4011        serde_json::Value::String(type_id.to_owned()),
4012    );
4013    for (name, value) in fields {
4014        object.insert(name.to_owned(), value);
4015    }
4016    serde_json::to_string(&object).map_err(serialization_error)
4017}
4018
4019fn decode_typed_json(
4020    value: &str,
4021    expected_type: &str,
4022    fields: &[&str],
4023) -> Result<serde_json::Map<String, serde_json::Value>, NoetherError> {
4024    let value: serde_json::Value = serde_json::from_str(value).map_err(serialization_error)?;
4025    let mut object = value.as_object().cloned().ok_or_else(|| {
4026        NoetherError::Serialization("a typed curve value must be a JSON object".to_owned())
4027    })?;
4028    let actual_type = object
4029        .remove("type")
4030        .and_then(|value| value.as_str().map(str::to_owned))
4031        .ok_or_else(|| {
4032            NoetherError::Serialization("a typed curve value has no string type field".to_owned())
4033        })?;
4034    if actual_type != expected_type {
4035        return Err(NoetherError::Serialization(format!(
4036            "expected type {expected_type}, received {actual_type}"
4037        )));
4038    }
4039    let mut actual = object.keys().map(String::as_str).collect::<Vec<_>>();
4040    actual.sort_unstable();
4041    let mut expected = fields.to_vec();
4042    expected.sort_unstable();
4043    if actual != expected {
4044        return Err(NoetherError::Serialization(format!(
4045            "serialized {expected_type} fields do not match the canonical schema"
4046        )));
4047    }
4048    Ok(object)
4049}
4050
4051fn decode_field<T: DeserializeOwned>(
4052    object: &mut serde_json::Map<String, serde_json::Value>,
4053    name: &str,
4054) -> Result<T, NoetherError> {
4055    let value = object.remove(name).ok_or_else(|| {
4056        NoetherError::Serialization(format!("serialized curve value has no {name} field"))
4057    })?;
4058    serde_json::from_value(value).map_err(serialization_error)
4059}
4060
4061fn nested_json(value: String) -> Result<serde_json::Value, NoetherError> {
4062    serde_json::from_str(&value).map_err(serialization_error)
4063}
4064
4065fn encode_nested_field(
4066    object: &mut serde_json::Map<String, serde_json::Value>,
4067    name: &str,
4068) -> Result<String, NoetherError> {
4069    serde_json::to_string(&object.remove(name).ok_or_else(|| {
4070        NoetherError::Serialization(format!("serialized curve value has no {name} field"))
4071    })?)
4072    .map_err(serialization_error)
4073}
4074
4075#[cfg(test)]
4076mod tests {
4077    use crate::{
4078        commalg::{
4079            Curve, CurveFunctionEvaluation, CurveFunctionOrder, CurveMorphism,
4080            RationalFunctionField, WeilDivisor,
4081        },
4082        Integer, Rational, Ring, Q,
4083    };
4084
4085    fn rational(value: i64) -> Rational {
4086        Rational::from(value)
4087    }
4088
4089    #[test]
4090    fn weierstrass_function_field_reduces_and_inverts_canonically() {
4091        let curve = Curve::smooth_projective_weierstrass(
4092            Q,
4093            [
4094                rational(0),
4095                rational(0),
4096                rational(0),
4097                rational(-1),
4098                rational(0),
4099            ],
4100        )
4101        .unwrap();
4102        assert_eq!(curve.smoothness_evidence().quotient_dimension(), 2);
4103        assert!(curve.smoothness_evidence().singular_cone_dimension() <= 0);
4104        curve.verify().unwrap();
4105
4106        let functions = curve.function_field().unwrap();
4107        assert_eq!(functions.extension_degree(), 2);
4108        let x = functions.x().unwrap();
4109        let y = functions.y().unwrap();
4110        let y_squared = y.pow(2).unwrap();
4111        let x_cubed_minus_x = (&x.pow(3).unwrap() - &x).unwrap();
4112        assert_eq!(y_squared, x_cubed_minus_x);
4113
4114        let value = (&x + &y).unwrap();
4115        let inverse = value.checked_inverse().unwrap();
4116        assert_eq!((&value * &inverse).unwrap(), functions.one());
4117        value.verify().unwrap();
4118        functions.verify().unwrap();
4119    }
4120
4121    #[test]
4122    fn generic_smooth_plane_curve_gets_a_finite_function_field_presentation() {
4123        let ring = crate::commalg::PolynomialRing::new(
4124            Q,
4125            ["u", "v", "w"],
4126            crate::commalg::MonomialOrder::GradedReverseLexicographic,
4127        )
4128        .unwrap();
4129        let equation = ring
4130            .from_terms([
4131                (vec![3.into(), 0.into(), 0.into()], rational(1)),
4132                (vec![0.into(), 3.into(), 0.into()], rational(1)),
4133                (vec![0.into(), 0.into(), 3.into()], rational(1)),
4134            ])
4135            .unwrap();
4136        let curve = Curve::smooth_projective_plane(equation).unwrap();
4137        let functions = curve.function_field().unwrap();
4138        assert_eq!(functions.extension_degree(), 3);
4139        assert_eq!(
4140            functions.modulus().leading_coefficient(),
4141            Some(functions.rational_subfield().one())
4142        );
4143
4144        let constants = WeilDivisor::zero(curve).riemann_roch_space().unwrap();
4145        assert_eq!(constants.dimension(), 1);
4146        assert!(constants.basis()[0].is_one());
4147        constants.verify().unwrap();
4148    }
4149
4150    #[test]
4151    fn singular_plane_and_weierstrass_models_fail_closed() {
4152        assert!(Curve::smooth_projective_weierstrass(
4153            Q,
4154            [
4155                rational(0),
4156                rational(0),
4157                rational(0),
4158                rational(0),
4159                rational(0)
4160            ]
4161        )
4162        .is_err());
4163
4164        let ring = crate::commalg::PolynomialRing::new(
4165            Q,
4166            ["x", "y", "z"],
4167            crate::commalg::MonomialOrder::GradedReverseLexicographic,
4168        )
4169        .unwrap();
4170        let x = ring.generator(0).unwrap();
4171        let singular = x.pow(2).unwrap();
4172        assert!(Curve::smooth_projective_plane(singular).is_err());
4173    }
4174
4175    #[test]
4176    fn local_evaluation_distinguishes_regular_values_zeros_and_poles() {
4177        let curve = Curve::smooth_projective_weierstrass(
4178            Q,
4179            [
4180                rational(0),
4181                rational(0),
4182                rational(0),
4183                rational(-1),
4184                rational(0),
4185            ],
4186        )
4187        .unwrap();
4188        let point = curve
4189            .point([rational(0), rational(0), rational(1)])
4190            .unwrap();
4191        let infinity = curve
4192            .point([rational(0), rational(1), rational(0)])
4193            .unwrap();
4194        let functions = curve.function_field().unwrap();
4195        let x = functions.x().unwrap();
4196        let y = functions.y().unwrap();
4197        let inverse_x = x.checked_inverse().unwrap();
4198        let x_plus_one = (&x + &functions.one()).unwrap();
4199
4200        assert_eq!(
4201            x.order_at(&point).unwrap(),
4202            CurveFunctionOrder::Finite(Integer::from(2))
4203        );
4204        assert_eq!(
4205            x.evaluate_at(&point).unwrap(),
4206            CurveFunctionEvaluation::Zero {
4207                order: Some(Integer::from(2))
4208            }
4209        );
4210        assert_eq!(
4211            inverse_x.evaluate_at(&point).unwrap(),
4212            CurveFunctionEvaluation::Pole {
4213                order: Integer::from(2)
4214            }
4215        );
4216        assert_eq!(
4217            x_plus_one.evaluate_at(&point).unwrap(),
4218            CurveFunctionEvaluation::Regular(rational(1))
4219        );
4220        assert_eq!(
4221            x.order_at(&infinity).unwrap(),
4222            CurveFunctionOrder::Finite(Integer::from(-2))
4223        );
4224        assert_eq!(
4225            y.order_at(&infinity).unwrap(),
4226            CurveFunctionOrder::Finite(Integer::from(-3))
4227        );
4228        let principal_x = x.principal_divisor().unwrap();
4229        let point_place = point.as_closed_point().unwrap();
4230        let infinity_place = infinity.as_closed_point().unwrap();
4231        assert_eq!(
4232            principal_x.coefficient(&point_place).unwrap(),
4233            Integer::from(2)
4234        );
4235        assert_eq!(
4236            principal_x.coefficient(&infinity_place).unwrap(),
4237            Integer::from(-2)
4238        );
4239        x.verify_principal_divisor(&principal_x).unwrap();
4240        point.verify().unwrap();
4241        infinity.verify().unwrap();
4242    }
4243
4244    #[test]
4245    fn rational_points_embed_as_degree_one_places_and_divisors_are_canonical() {
4246        let curve = Curve::smooth_projective_weierstrass(
4247            Q,
4248            [
4249                rational(0),
4250                rational(0),
4251                rational(0),
4252                rational(-1),
4253                rational(0),
4254            ],
4255        )
4256        .unwrap();
4257        let point = curve
4258            .point([rational(0), rational(0), rational(1)])
4259            .unwrap()
4260            .as_closed_point()
4261            .unwrap();
4262        let infinity = curve
4263            .point([rational(0), rational(1), rational(0)])
4264            .unwrap()
4265            .as_closed_point()
4266            .unwrap();
4267        assert_eq!(point.degree(), &Integer::one());
4268        assert!(point.rational_point().is_some());
4269
4270        let divisor = WeilDivisor::new(
4271            curve.clone(),
4272            [
4273                (point.clone(), Integer::from(-2)),
4274                (infinity.clone(), Integer::from(5)),
4275                (point.clone(), Integer::from(-3)),
4276            ],
4277        )
4278        .unwrap();
4279        assert_eq!(divisor.terms().len(), 2);
4280        assert_eq!(divisor.coefficient(&point).unwrap(), Integer::from(-5));
4281        assert_eq!(divisor.coefficient(&infinity).unwrap(), Integer::from(5));
4282        assert_eq!(divisor.degree(), Integer::zero());
4283        assert_eq!(divisor.positive_part().degree(), Integer::from(5));
4284        assert_eq!(divisor.negative_part().degree(), Integer::from(5));
4285        assert_eq!((&divisor + &(-&divisor)).unwrap(), WeilDivisor::zero(curve));
4286        divisor.verify().unwrap();
4287        let identity = CurveMorphism::identity(divisor.curve().clone()).unwrap();
4288        assert_eq!(divisor.pullback(&identity).unwrap(), divisor);
4289        identity.verify().unwrap();
4290        let ring = divisor.curve().equation().ring();
4291        let scaled_identity = CurveMorphism::new(
4292            divisor.curve().clone(),
4293            divisor.curve().clone(),
4294            [
4295                ring.generator(0).unwrap().scalar_mul(&rational(2)).unwrap(),
4296                ring.generator(1).unwrap().scalar_mul(&rational(2)).unwrap(),
4297                ring.generator(2).unwrap().scalar_mul(&rational(2)).unwrap(),
4298            ],
4299        )
4300        .unwrap();
4301        assert_eq!(scaled_identity, identity);
4302
4303        let pole_divisor =
4304            WeilDivisor::new(infinity.curve().clone(), [(infinity, Integer::from(2))]).unwrap();
4305        let space = pole_divisor.riemann_roch_space().unwrap();
4306        assert_eq!(space.dimension(), 2);
4307        space.verify().unwrap();
4308    }
4309
4310    #[test]
4311    fn fisher_order_five_riemann_roch_space_has_the_expected_generator() {
4312        let coefficients = RationalFunctionField::new(Q, "lambda").unwrap();
4313        let lambda = coefficients.generator();
4314        let one = coefficients.one();
4315        let zero = coefficients.zero();
4316        let one_minus_lambda = (&one - &lambda).unwrap();
4317        let minus_lambda = (-&lambda).unwrap();
4318        let curve = Curve::smooth_projective_weierstrass(
4319            coefficients.clone(),
4320            [
4321                one_minus_lambda,
4322                minus_lambda.clone(),
4323                minus_lambda,
4324                zero.clone(),
4325                zero.clone(),
4326            ],
4327        )
4328        .unwrap();
4329        let point = curve
4330            .point([zero.clone(), zero.clone(), one.clone()])
4331            .unwrap()
4332            .as_closed_point()
4333            .unwrap();
4334        let infinity = curve
4335            .point([zero.clone(), one.clone(), zero])
4336            .unwrap()
4337            .as_closed_point()
4338            .unwrap();
4339        let divisor = WeilDivisor::new(
4340            curve.clone(),
4341            [(infinity, Integer::from(5)), (point, Integer::from(-5))],
4342        )
4343        .unwrap();
4344        let space = divisor.riemann_roch_space().unwrap();
4345        assert_eq!(space.dimension(), 1);
4346
4347        let functions = curve.function_field().unwrap();
4348        let x = functions.x().unwrap();
4349        let y = functions.y().unwrap();
4350        let minus_x_minus_one = (-&(&x + &functions.one()).unwrap()).unwrap();
4351        let expected = (&(&minus_x_minus_one * &y).unwrap() + &x.pow(2).unwrap()).unwrap();
4352        let quotient = space.basis()[0].checked_div(&expected).unwrap();
4353        assert!(quotient.as_constant().unwrap().is_some());
4354    }
4355
4356    #[test]
4357    fn principal_divisor_retains_a_quadratic_closed_point() {
4358        let curve = Curve::smooth_projective_weierstrass(
4359            Q,
4360            [
4361                rational(0),
4362                rational(0),
4363                rational(0),
4364                rational(-1),
4365                rational(0),
4366            ],
4367        )
4368        .unwrap();
4369        let functions = curve.function_field().unwrap();
4370        let x_minus_two =
4371            (&functions.x().unwrap() - &functions.constant(rational(2)).unwrap()).unwrap();
4372        let divisor = x_minus_two.principal_divisor().unwrap();
4373        assert_eq!(divisor.degree(), Integer::zero());
4374        let quadratic = divisor
4375            .terms()
4376            .iter()
4377            .find(|(point, coefficient)| {
4378                point.degree() == &Integer::from(2) && coefficient == &Integer::one()
4379            })
4380            .map(|(point, _)| point)
4381            .expect("the conjugate roots define one quadratic place");
4382        assert!(quadratic.rational_point().is_none());
4383        assert!(divisor
4384            .terms()
4385            .iter()
4386            .any(|(point, coefficient)| point.degree() == &Integer::one()
4387                && coefficient == &Integer::from(-2)));
4388    }
4389
4390    #[test]
4391    fn curve_values_round_trip_through_revalidated_typed_json() {
4392        let curve = Curve::smooth_projective_weierstrass(
4393            Q,
4394            [
4395                rational(0),
4396                rational(0),
4397                rational(0),
4398                rational(-1),
4399                rational(0),
4400            ],
4401        )
4402        .unwrap();
4403        let functions = curve.function_field().unwrap();
4404        let point = curve
4405            .point([rational(0), rational(0), rational(1)])
4406            .unwrap();
4407        let infinity = curve
4408            .point([rational(0), rational(1), rational(0)])
4409            .unwrap();
4410        let function = (&functions.x().unwrap() + &functions.y().unwrap()).unwrap();
4411        let divisor = functions.x().unwrap().principal_divisor().unwrap();
4412        let poles = WeilDivisor::new(
4413            curve.clone(),
4414            [(infinity.as_closed_point().unwrap(), Integer::from(2))],
4415        )
4416        .unwrap();
4417        let space = poles.riemann_roch_space().unwrap();
4418        let identity = CurveMorphism::identity(curve.clone()).unwrap();
4419
4420        assert_eq!(
4421            Curve::<crate::RationalField>::from_json(&curve.to_json().unwrap()).unwrap(),
4422            curve
4423        );
4424        assert_eq!(
4425            super::CurveFunctionField::<crate::RationalField>::from_json(
4426                &functions.to_json().unwrap()
4427            )
4428            .unwrap(),
4429            functions
4430        );
4431        assert_eq!(
4432            super::CurvePoint::<crate::RationalField>::from_json(&point.to_json().unwrap())
4433                .unwrap(),
4434            point
4435        );
4436        assert_eq!(
4437            super::CurveClosedPoint::<crate::RationalField>::from_json(
4438                &point.as_closed_point().unwrap().to_json().unwrap()
4439            )
4440            .unwrap(),
4441            point.as_closed_point().unwrap()
4442        );
4443        assert_eq!(
4444            super::CurveFunction::<crate::RationalField>::from_json(&function.to_json().unwrap())
4445                .unwrap(),
4446            function
4447        );
4448        let shared_denominator = (&(&functions.one() + &functions.y().unwrap()).unwrap()
4449            / &functions.x().unwrap())
4450            .unwrap();
4451        assert_eq!(
4452            super::CurveFunction::<crate::RationalField>::from_json(
4453                &shared_denominator.to_json().unwrap()
4454            )
4455            .unwrap(),
4456            shared_denominator
4457        );
4458        assert_eq!(
4459            super::WeilDivisor::<crate::RationalField>::from_json(&divisor.to_json().unwrap())
4460                .unwrap(),
4461            divisor
4462        );
4463        assert_eq!(
4464            super::RiemannRochSpace::<crate::RationalField>::from_json(&space.to_json().unwrap())
4465                .unwrap(),
4466            space
4467        );
4468        assert_eq!(
4469            super::CurveMorphism::<crate::RationalField>::from_json(&identity.to_json().unwrap())
4470                .unwrap(),
4471            identity
4472        );
4473
4474        let x_minus_two =
4475            (&functions.x().unwrap() - &functions.constant(rational(2)).unwrap()).unwrap();
4476        let quadratic = x_minus_two
4477            .principal_divisor()
4478            .unwrap()
4479            .support()
4480            .find(|point| point.degree() == &Integer::from(2))
4481            .cloned()
4482            .unwrap();
4483        assert_eq!(
4484            super::CurveClosedPoint::<crate::RationalField>::from_json(
4485                &quadratic.to_json().unwrap()
4486            )
4487            .unwrap(),
4488            quadratic
4489        );
4490
4491        let mut tampered: serde_json::Value =
4492            serde_json::from_str(&function.to_json().unwrap()).unwrap();
4493        let patch = curve.equation.ring().generator(2).unwrap();
4494        let (numerator, denominator) = function.homogeneous_representation().unwrap();
4495        tampered["numerator"] = serde_json::to_value((&numerator * &patch).unwrap()).unwrap();
4496        tampered["denominator"] = serde_json::to_value((&denominator * &patch).unwrap()).unwrap();
4497        assert!(super::CurveFunction::<crate::RationalField>::from_json(
4498            &serde_json::to_string(&tampered).unwrap()
4499        )
4500        .is_err());
4501    }
4502
4503    #[test]
4504    fn finite_field_nonrational_places_revalidate_from_json() {
4505        let field = crate::numthy::FiniteField::prime(5).unwrap();
4506        let curve = Curve::smooth_projective_weierstrass(
4507            field.clone(),
4508            [
4509                field.zero(),
4510                field.zero(),
4511                field.zero(),
4512                field.one(),
4513                field.one(),
4514            ],
4515        )
4516        .unwrap();
4517        let functions = curve.function_field().unwrap();
4518        let x_minus_one = (&functions.x().unwrap() - &functions.one()).unwrap();
4519        let divisor = x_minus_one.principal_divisor().unwrap();
4520        let quadratic = divisor
4521            .support()
4522            .find(|point| point.degree() == &Integer::from(2))
4523            .cloned()
4524            .unwrap();
4525
4526        assert_eq!(
4527            super::CurveClosedPoint::<crate::numthy::FiniteField>::from_json(
4528                &quadratic.to_json().unwrap()
4529            )
4530            .unwrap(),
4531            quadratic
4532        );
4533        assert_eq!(
4534            super::WeilDivisor::<crate::numthy::FiniteField>::from_json(
4535                &divisor.to_json().unwrap()
4536            )
4537            .unwrap(),
4538            divisor
4539        );
4540    }
4541}