From 94262db8ddc94b820afd9e8d17f1ff2fbf9a6f73 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Sat, 3 Feb 2024 17:23:51 +0100 Subject: [PATCH] compute the endomorphism order of an elliptic curve over a finite field (for the rank-2 case) --- .../elliptic_curves/ell_finite_field.py | 126 +++++++++++++++++- 1 file changed, 119 insertions(+), 7 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index aa8385a9672..a9efa8f1965 100755 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -680,6 +680,10 @@ def frobenius_order(self): This computes the curve cardinality, which may be time-consuming. + .. SEEALSO:: + + :meth:`endomorphism_order` + EXAMPLES:: sage: E = EllipticCurve(GF(11),[3,3]) @@ -1527,7 +1531,11 @@ def _fetch_cached_order(self, other): def height_above_floor(self, ell, e): r""" - Return the height of the `j`-invariant of this ordinary elliptic curve on its `\ell`-volcano. + Return the height of the `j`-invariant of this elliptic curve on its `\ell`-volcano. + + The curve must have a rational endomorphism ring of rank 2: This includes all + ordinary elliptic curves over finite fields as well as those supersingular + elliptic curves with Frobenius not in `\ZZ`. INPUT: @@ -1538,12 +1546,12 @@ def height_above_floor(self, ell, e): .. NOTE:: - For an ordinary `E/\GF{q}`, and a prime `\ell`, the height - `e` of the `\ell`-volcano containing `j(E)` is the `\ell`-adic + For a suitable `E/\GF{q}`, and a prime `\ell`, the height + `e` of the `\ell`-volcano containing `E` is the `\ell`-adic valuation of the conductor of the order generated by the - Frobenius `\pi_E`; the height of `j(E)` on its + `\GF{q}`-Frobenius `\pi_E`; the height of `E` on its ell-volcano is the `\ell`-adic valuation of the conductor - of the order `\text{End}(E)`. + of the order `\text{End}_{\GF{q}}(E)`. ALGORITHM: @@ -1560,10 +1568,51 @@ def height_above_floor(self, ell, e): sage: E.height_above_floor(2,8) 5 """ - if self.is_supersingular(): - raise ValueError("{} is not ordinary".format(self)) + pi = self.frobenius() + if pi in ZZ: + raise ValueError("{} has a (rational) endomorphism ring of rank 4".format(self)) + + e = ZZ(e) if not e: return ZZ.zero() + + if self.is_supersingular(): + if ell == self.base_field().characteristic(): + # In this (exceptional) case, the Frobenius can always be divided + # by the maximal possible power of the characteristic. The reason + # is that Frobenius must be of the form phi o [p^k] where phi is + # a purely inseparable isogeny of degree 1 or p, hence this [p^k] + # can always be divided out while retaining an endomorphism. + assert (ell**(2*e)).divides(self.base_field().cardinality()) + return e + + # In the supersingular case, the j-invariant alone does not determine + # the level in the volcano. (The underlying reason is that there can + # be multiple non-F_q-isomorphic curves with a given j-invariant in + # the isogeny graph.) + # Example: y^2 = x^3 ± x over F_p with p congruent to 3 modulo 4 have + # distinct (rational) endomorphism rings. + # Thus we run the "probing the depths" algorithm with F_q-isomorphism + # classes of curves instead. + E0 = [self] * 3 + E1 = [phi.codomain() for phi in self.isogenies_prime_degree(ell)] + assert E1 + if len(E1) == 1: + return ZZ.zero() + assert len(E1) == ell + 1 + h = ZZ.one() + while True: + for i in range(3): + isogs = E1[i].isogenies_prime_degree(ell) + try: + step = next(phi for phi in isogs if not phi.codomain().is_isomorphic(E0[i])) + except StopIteration: + return h + E0[i], E1[i] = step.domain(), step.codomain() + h += 1 + assert h <= e + raise NotImplementedError + j = self.j_invariant() if j in [0, 1728]: return e @@ -1668,6 +1717,69 @@ def endomorphism_discriminant_from_class_number(self, h): return (v//cs[0])**2 * D0 raise ValueError("Incorrect class number {}".format(h)) + def endomorphism_order(self): + r""" + Return a quadratic order isomorphic to the endomorphism ring + of this elliptic curve, assuming the order has rank two. + + .. NOTE:: + + In the future, this method will hopefully be extended to return a + :class:`~sage.algebras.quatalg.quaternion_algebra.QuaternionOrder` + object in the rank-4 case, but this has not been implemented yet. + + .. SEEALSO:: + + :meth:`frobenius_order` + + EXAMPLES:: + + sage: E = EllipticCurve(GF(11), [3,3]) + sage: E.endomorphism_order() + Maximal Order generated by 1/2*pi + 1/2 in Number Field in pi with defining polynomial x^2 - 4*x + 11 + + It also works for supersingular elliptic curves provided that Frobenius + is not in `\ZZ`:: + + sage: E = EllipticCurve(GF(11), [1,0]) + sage: E.is_supersingular() + True + sage: E.endomorphism_order() + Order of conductor 2 generated by pi in Number Field in pi with defining polynomial x^2 + 11 + + :: + + sage: E = EllipticCurve(GF(11), [-1,0]) + sage: E.is_supersingular() + True + sage: E.endomorphism_order() + Maximal Order generated by 1/2*pi + 1/2 in Number Field in pi with defining polynomial x^2 + 11 + + There are some exceptional cases where Frobenius itself is divisible + by the characteristic:: + + sage: EllipticCurve([GF(7^2).gen(), 0]).endomorphism_order() + Gaussian Integers generated by 1/7*pi in Number Field in pi with defining polynomial x^2 + 49 + sage: EllipticCurve(GF(3^5), [1, 0]).endomorphism_order() + Order of conductor 2 generated by 1/9*pi in Number Field in pi with defining polynomial x^2 + 243 + sage: EllipticCurve(GF(7^3), [-1, 0]).endomorphism_order() + Maximal Order generated by 1/14*pi + 1/2 in Number Field in pi with defining polynomial x^2 + 343 + """ + pi = self.frobenius() + if pi in ZZ: + raise NotImplementedError('the rank-4 case is not supported yet') + + O = self.frobenius_order() + f0 = O.conductor() + + f = 1 + for l,e in f0.factor(): + h = self.height_above_floor(l, e) + f *= l**(e-h) + + K = O.number_field() + return K.order_of_conductor(f) + def twists(self): r""" Return a list of `k`-isomorphism representatives of all