1use 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#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum CurveError {
78 Scheme(SchemeError),
80 PolynomialContext(PolynomialContextError),
82 Polynomial(PolynomialError),
84 Groebner(GroebnerError),
86 LinearAlgebra(LinalgError),
88 RationalFunction(RationalFunctionError),
90 Factorization(PolynomialFactorizationError),
92 ContextMismatch(ContextMismatchError),
94 InvalidPlaneEquation,
96 NotOneDimensional,
98 SingularCurve,
100 SingularWeierstrassEquation,
102 NoFunctionFieldPresentation,
104 DivisionByZero,
106 LocalExpansionLimit {
108 limit: usize,
110 },
111 ResourceLimit {
113 resource: &'static str,
115 limit: usize,
117 },
118 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub enum CurvePresentationKind {
235 SmoothProjectivePlane,
237 SmoothProjectiveWeierstrass,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct CurveSmoothnessEvidence {
244 quotient_dimension: i64,
245 singular_cone_dimension: i64,
246}
247
248impl CurveSmoothnessEvidence {
249 pub fn quotient_dimension(&self) -> i64 {
251 self.quotient_dimension
252 }
253
254 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#[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 pub fn smooth_projective_plane(equation: Polynomial<R>) -> Result<Self, CurveError> {
283 Self::from_plane_equation(equation, CurvePresentation::Plane)
284 }
285
286 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 pub fn base(&self) -> &R {
368 self.scheme.base()
369 }
370
371 pub fn scheme(&self) -> &Scheme<R> {
373 &self.scheme
374 }
375
376 pub fn equation(&self) -> &Polynomial<R> {
378 &self.equation
379 }
380
381 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 pub fn smoothness_evidence(&self) -> &CurveSmoothnessEvidence {
393 &self.evidence
394 }
395
396 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 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 pub fn function_field(&self) -> Result<CurveFunctionField<R>, CurveError> {
416 CurveFunctionField::new(self.clone())
417 }
418
419 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 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 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 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#[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 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 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 pub fn source(&self) -> &Curve<R> {
668 &self.source
669 }
670
671 pub fn target(&self) -> &Curve<R> {
673 &self.target
674 }
675
676 pub fn coordinate_images(&self) -> &[Polynomial<R>; 3] {
678 &self.coordinate_images
679 }
680
681 pub fn coordinate_degree(&self) -> &Integer {
683 &self.coordinate_degree
684 }
685
686 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 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 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#[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 pub fn curve(&self) -> &Curve<R> {
815 &self.curve
816 }
817
818 pub fn coordinates(&self) -> &[R::Element] {
820 self.point.coordinates()
821 }
822
823 pub fn scheme_point(&self) -> &SchemePoint<R> {
825 &self.point
826 }
827
828 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 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 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 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#[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 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 pub fn curve(&self) -> &Curve<R> {
1022 &self.curve
1023 }
1024
1025 pub fn prime_ideal(&self) -> &PrimeIdeal<PolynomialRing<R>> {
1027 &self.prime_ideal
1028 }
1029
1030 pub fn degree(&self) -> &Integer {
1032 &self.degree
1033 }
1034
1035 pub fn rational_point(&self) -> Option<&CurvePoint<R>> {
1037 self.rational_point.as_ref()
1038 }
1039
1040 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 pub fn to_json(&self) -> Result<String, NoetherError> {
1344 closed_point_to_json(self)
1345 }
1346
1347 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 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 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#[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 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 pub fn zero(curve: Curve<R>) -> Self {
1437 Self {
1438 curve,
1439 terms: Vec::new(),
1440 }
1441 }
1442
1443 pub fn curve(&self) -> &Curve<R> {
1445 &self.curve
1446 }
1447
1448 pub fn terms(&self) -> &[(CurveClosedPoint<R>, Integer)] {
1450 &self.terms
1451 }
1452
1453 pub fn support(&self) -> impl Iterator<Item = &CurveClosedPoint<R>> {
1455 self.terms.iter().map(|(point, _)| point)
1456 }
1457
1458 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 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 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 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 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 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 pub fn checked_sub(&self, other: &Self) -> Result<Self, CurveError> {
1544 self.checked_add(&other.scale(&Integer::from(-1)))
1545 }
1546
1547 pub fn negated(&self) -> Self {
1549 self.scale(&Integer::from(-1))
1550 }
1551
1552 pub fn riemann_roch_space(&self) -> Result<RiemannRochSpace<R>, CurveError> {
1554 compute_riemann_roch_space(self)
1555 }
1556
1557 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 pub fn to_json(&self) -> Result<String, NoetherError> {
1712 divisor_to_json(self)
1713 }
1714
1715 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#[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 pub fn divisor(&self) -> &WeilDivisor<R> {
1769 &self.divisor
1770 }
1771
1772 pub fn dimension(&self) -> usize {
1774 self.basis.len()
1775 }
1776
1777 pub fn basis(&self) -> &[CurveFunction<R>] {
1779 &self.basis
1780 }
1781
1782 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 pub fn to_json(&self) -> Result<String, NoetherError> {
1893 riemann_roch_to_json(self)
1894 }
1895
1896 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(§ion_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 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(§ion)?;
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#[derive(Debug, Clone, PartialEq, Eq)]
2260pub enum CurveFunctionOrder {
2261 Finite(Integer),
2263 Infinite,
2265}
2266
2267#[derive(Debug, Clone, PartialEq, Eq)]
2269pub enum CurveFunctionEvaluation<R: Field> {
2270 Regular(R::Element),
2272 Zero {
2274 order: Option<Integer>,
2276 },
2277 Pole {
2279 order: Integer,
2281 },
2282}
2283
2284#[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 pub fn curve(&self) -> &Curve<R> {
2368 &self.curve
2369 }
2370
2371 pub fn transcendental_coordinate(&self) -> usize {
2373 self.transcendental_coordinate
2374 }
2375
2376 pub fn algebraic_coordinate(&self) -> usize {
2378 self.algebraic_coordinate
2379 }
2380
2381 pub fn patch_coordinate(&self) -> usize {
2383 self.patch_coordinate
2384 }
2385
2386 pub fn rational_subfield(&self) -> &RationalFunctionField<R> {
2388 &self.rational_field
2389 }
2390
2391 pub fn modulus(&self) -> &Polynomial<RationalFunctionField<R>> {
2393 &self.modulus
2394 }
2395
2396 pub fn extension_degree(&self) -> usize {
2398 self.extension_degree
2399 }
2400
2401 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 pub fn y(&self) -> Result<CurveFunction<R>, CurveError> {
2411 self.from_representative(self.extension_ring.generator(0)?)
2412 }
2413
2414 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 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 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 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 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 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 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 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#[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 pub fn field(&self) -> &CurveFunctionField<R> {
2746 &self.field
2747 }
2748
2749 pub fn representative(&self) -> &Polynomial<RationalFunctionField<R>> {
2751 &self.representative
2752 }
2753
2754 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 pub fn is_zero(&self) -> bool {
2769 self.representative.is_zero()
2770 }
2771
2772 pub fn is_one(&self) -> bool {
2774 self.representative == self.field.extension_ring.one()
2775 }
2776
2777 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 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 pub fn checked_inverse(&self) -> Result<Self, CurveError> {
2835 self.field.checked_inverse(self)
2836 }
2837
2838 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 pub fn order_at(&self, point: &CurvePoint<R>) -> Result<CurveFunctionOrder, CurveError> {
2850 self.order_at_with_limit(point, 256)
2851 }
2852
2853 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 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 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 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 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 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 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 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 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 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 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 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 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, °ree)?;
3219 let denominator = homogenize_xy_terms(field, denominator_terms, °ree)?;
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 * °ree;
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, °ree - &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(°ree)?);
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 = ("ient * &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}