Skip to content

Commit 5905da7

Browse files
author
Release Manager
committedJan 2, 2023
Trac #33915: inseparable elliptic-curve isogenies
We implement an `EllipticCurveHom` child class `EllipticCurveHom_frobenius` encapsulating purely inseparable ''Frobenius isogenies''. As every isogeny decomposes into a separable and a purely inseparable part, we can (together with `EllipticCurveHom_composite`) now express any isogeny between two elliptic curves in Sage. One immediate application (also implemented in the patch) is that separable isogenies of degree divisible by the characteristic now have a working `.dual()` method. Other than that, changes to the existing codebase are kept minimal. This is one of the items on the "isogeny wish-list" #7368. It is also an important step towards implementing endomorphism rings later; cf. comment:3:ticket:32826. Diff without the dependency: https://git.sagemath.org/sage.git/diff?id2= 79ae468&id=e953939d23995c0c26964dc969fa 69cea52ee1c4 URL: https://trac.sagemath.org/33915 Reported by: lorenz Ticket author(s): Lorenz Panny, Mickaël Montessinos Reviewer(s): John Cremona
2 parents 98b22eb + 2ebbfae commit 5905da7

File tree

7 files changed

+784
-42
lines changed

7 files changed

+784
-42
lines changed
 

‎src/doc/en/reference/arithmetic_curves/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Maps between them
2323
sage/schemes/elliptic_curves/hom_velusqrt
2424
sage/schemes/elliptic_curves/hom_composite
2525
sage/schemes/elliptic_curves/hom_scalar
26+
sage/schemes/elliptic_curves/hom_frobenius
2627
sage/schemes/elliptic_curves/isogeny_small_degree
2728

2829

‎src/sage/schemes/elliptic_curves/ell_curve_isogeny.py

+82-34
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
use of univariate vs. bivariate polynomials and rational functions.
6868
6969
- Lorenz Panny (2022-04): major cleanup of code and documentation
70+
- Lorenz Panny (2022): inseparable duals
7071
"""
7172

7273
# ****************************************************************************
@@ -2954,6 +2955,31 @@ def dual(self):
29542955
sage: (Xm, Ym) == E.multiplication_by_m(5)
29552956
True
29562957
2958+
Inseparable duals should be computed correctly::
2959+
2960+
sage: z2 = GF(71^2).gen()
2961+
sage: E = EllipticCurve(j=57*z2+51)
2962+
sage: E.isogeny(3*E.lift_x(0)).dual()
2963+
Composite morphism of degree 71 = 71*1^2:
2964+
From: Elliptic Curve defined by y^2 = x^3 + (32*z2+67)*x + (24*z2+37) over Finite Field in z2 of size 71^2
2965+
To: Elliptic Curve defined by y^2 = x^3 + (41*z2+56)*x + (18*z2+42) over Finite Field in z2 of size 71^2
2966+
sage: E.isogeny(E.lift_x(0)).dual()
2967+
Composite morphism of degree 213 = 71*3:
2968+
From: Elliptic Curve defined by y^2 = x^3 + (58*z2+31)*x + (34*z2+58) over Finite Field in z2 of size 71^2
2969+
To: Elliptic Curve defined by y^2 = x^3 + (41*z2+56)*x + (18*z2+42) over Finite Field in z2 of size 71^2
2970+
2971+
...even if pre- or post-isomorphisms are present::
2972+
2973+
sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
2974+
sage: phi = E.isogeny(E.lift_x(0))
2975+
sage: pre = ~WeierstrassIsomorphism(phi.domain(), (z2,2,3,4))
2976+
sage: post = WeierstrassIsomorphism(phi.codomain(), (5,6,7,8))
2977+
sage: phi = post * phi * pre
2978+
sage: phi.dual()
2979+
Composite morphism of degree 213 = 71*3:
2980+
From: Elliptic Curve defined by y^2 + 17*x*y + 45*y = x^3 + 30*x^2 + (6*z2+64)*x + (48*z2+65) over Finite Field in z2 of size 71^2
2981+
To: Elliptic Curve defined by y^2 + (60*z2+22)*x*y + (69*z2+37)*y = x^3 + (32*z2+48)*x^2 + (19*z2+58)*x + (56*z2+22) over Finite Field in z2 of size 71^2
2982+
29572983
TESTS:
29582984
29592985
Test for :trac:`23928`::
@@ -3003,55 +3029,77 @@ def dual(self):
30033029
return self.__dual
30043030

30053031
# trac 7096
3006-
E1, E2pr, pre_isom, post_isom = compute_intermediate_curves(self.codomain(), self.domain())
3032+
E1, E2pr, _, _ = compute_intermediate_curves(self.codomain(), self.domain())
30073033

30083034
F = self.__base_field
30093035
d = self._degree
30103036

3011-
# trac 7096
3012-
if F(d) == 0:
3013-
raise NotImplementedError("the dual isogeny is not separable: only separable isogenies are currently implemented")
3014-
3015-
# trac 7096
3016-
# this should take care of the case when the isogeny is not normalized.
3017-
u = self.scaling_factor()
3018-
isom = WeierstrassIsomorphism(E2pr, (u/F(d), 0, 0, 0))
3019-
3020-
E2 = isom.codomain()
3037+
from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
30213038

3022-
pre_isom = self._codomain.isomorphism_to(E1)
3023-
post_isom = E2.isomorphism_to(self._domain)
3039+
if F(d) == 0: # inseparable dual!
3040+
p = F.characteristic()
3041+
k = d.valuation(p)
30243042

3025-
phi_hat = EllipticCurveIsogeny(E1, None, E2, d)
3043+
from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
3044+
frob = EllipticCurveHom_frobenius(self._codomain, k)
30263045

3027-
phi_hat._set_pre_isomorphism(pre_isom)
3028-
phi_hat._set_post_isomorphism(post_isom)
3029-
phi_hat.__perform_inheritance_housekeeping()
3046+
dsep = d // p**k
3047+
if dsep > 1:
3048+
#TODO: We could also use resultants here; this is much
3049+
# faster in some cases (but seems worse in general).
3050+
# Presumably there should be a wrapper function that
3051+
# decides on the fly which method to use.
3052+
# Eventually this should become a .separable_part() method.
30303053

3031-
assert phi_hat.codomain() == self.domain()
3054+
f = self.kernel_polynomial()
30323055

3033-
# trac 7096 : this adjusts a posteriori the automorphism on
3034-
# the codomain of the dual isogeny. we used _a_ Weierstrass
3035-
# isomorphism to get to the original curve, but we may have to
3036-
# change it by an automorphism. We impose the condition that
3037-
# the composition has the degree as a leading coefficient in
3038-
# the formal expansion.
3056+
psi = self._domain.division_polynomial(p)
3057+
mu_num = self._domain._multiple_x_numerator(p)
3058+
mu_den = self._domain._multiple_x_denominator(p)
30393059

3040-
phihat_sc = phi_hat.scaling_factor()
3060+
for _ in range(k):
3061+
f //= f.gcd(psi)
3062+
S = f.parent().quotient_ring(f)
3063+
mu = S(mu_num) / S(mu_den)
3064+
f = mu.minpoly()
30413065

3042-
sc = u * phihat_sc/F(d)
3066+
sep = self._domain.isogeny(f, codomain=frob.codomain()).dual()
30433067

3044-
assert sc != 0, "bug in dual()"
3068+
else:
3069+
sep = frob.codomain().isomorphism_to(self._domain)
30453070

3046-
if sc != 1:
3047-
auts = self._domain.automorphisms()
3048-
aut = [a for a in auts if a.u == sc]
3049-
assert len(aut) == 1, "bug in dual()"
3050-
phi_hat._set_post_isomorphism(aut[0])
3071+
phi_hat = EllipticCurveHom_composite.from_factors([frob, sep])
30513072

3052-
self.__dual = phi_hat
3073+
from sage.schemes.elliptic_curves.hom import find_post_isomorphism
3074+
mult = self._domain.scalar_multiplication(d)
3075+
rhs = phi_hat * self
3076+
corr = find_post_isomorphism(mult, rhs)
3077+
self.__dual = corr * phi_hat
3078+
return self.__dual
30533079

3054-
return phi_hat
3080+
else:
3081+
# trac 7096
3082+
# this should take care of the case when the isogeny is not normalized.
3083+
u = self.scaling_factor()
3084+
E2 = E2pr.change_weierstrass_model(u/F(d), 0, 0, 0)
3085+
3086+
phi_hat = EllipticCurveIsogeny(E1, None, E2, d)
3087+
3088+
pre_iso = self._codomain.isomorphism_to(E1)
3089+
post_iso = E2.isomorphism_to(self._domain)
3090+
3091+
# assert phi_hat.scaling_factor() == 1
3092+
sc = u * pre_iso.scaling_factor() * post_iso.scaling_factor() / F(d)
3093+
if not sc.is_one():
3094+
auts = self._codomain.automorphisms()
3095+
aut = [a for a in auts if a.u == sc]
3096+
assert len(aut) == 1, "bug in dual()"
3097+
pre_iso *= aut[0]
3098+
3099+
phi_hat._set_pre_isomorphism(pre_iso)
3100+
phi_hat._set_post_isomorphism(post_iso)
3101+
phi_hat.__perform_inheritance_housekeeping()
3102+
return phi_hat
30553103

30563104

30573105
@staticmethod

‎src/sage/schemes/elliptic_curves/ell_finite_field.py

+22
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,28 @@ def frobenius(self):
672672
else:
673673
return R.gen(1)
674674

675+
def frobenius_endomorphism(self):
676+
r"""
677+
Return the `q`-power Frobenius endomorphism of this elliptic
678+
curve, where `q` is the cardinality of the (finite) base field.
679+
680+
EXAMPLES::
681+
682+
sage: F.<t> = GF(11^4)
683+
sage: E = EllipticCurve([t,t])
684+
sage: E.frobenius_endomorphism()
685+
Frobenius endomorphism of degree 14641 = 11^4:
686+
From: Elliptic Curve defined by y^2 = x^3 + t*x + t over Finite Field in t of size 11^4
687+
To: Elliptic Curve defined by y^2 = x^3 + t*x + t over Finite Field in t of size 11^4
688+
sage: E.frobenius_endomorphism() == E.frobenius_isogeny(4)
689+
True
690+
691+
.. SEEALSO::
692+
693+
:meth:`~sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic.frobenius_isogeny`
694+
"""
695+
return self.frobenius_isogeny(self.base_field().degree())
696+
675697
def cardinality_pari(self):
676698
r"""
677699
Return the cardinality of ``self`` using PARI.

‎src/sage/schemes/elliptic_curves/ell_generic.py

+31
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,37 @@ def scalar_multiplication(self, m):
24042404
from sage.schemes.elliptic_curves.hom_scalar import EllipticCurveHom_scalar
24052405
return EllipticCurveHom_scalar(self, m)
24062406

2407+
def frobenius_isogeny(self, n=1):
2408+
r"""
2409+
Return the `n`-power Frobenius isogeny from this curve to
2410+
its Galois conjugate.
2411+
2412+
The Frobenius *endo*\morphism is the special case where `n`
2413+
is divisible by the degree of the base ring of the curve.
2414+
2415+
.. SEEALSO::
2416+
2417+
:meth:`~sage.schemes.elliptic_curves.ell_finite_field.EllipticCurve_finite_field.frobenius_endomorphism`
2418+
2419+
EXAMPLES::
2420+
2421+
sage: z3, = GF(13^3).gens()
2422+
sage: E = EllipticCurve([z3,z3^2])
2423+
sage: E.frobenius_isogeny()
2424+
Frobenius isogeny of degree 13:
2425+
From: Elliptic Curve defined by y^2 = x^3 + z3*x + z3^2 over Finite Field in z3 of size 13^3
2426+
To: Elliptic Curve defined by y^2 = x^3 + (5*z3^2+7*z3+11)*x + (5*z3^2+12*z3+1) over Finite Field in z3 of size 13^3
2427+
sage: E.frobenius_isogeny(3)
2428+
Frobenius endomorphism of degree 2197 = 13^3:
2429+
From: Elliptic Curve defined by y^2 = x^3 + z3*x + z3^2 over Finite Field in z3 of size 13^3
2430+
To: Elliptic Curve defined by y^2 = x^3 + z3*x + z3^2 over Finite Field in z3 of size 13^3
2431+
"""
2432+
p = self.base_ring().characteristic()
2433+
if not p:
2434+
raise ValueError('Frobenius isogeny only exists in positive characteristic')
2435+
from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
2436+
return EllipticCurveHom_frobenius(self, n)
2437+
24072438
def isomorphism_to(self, other):
24082439
"""
24092440
Given another weierstrass model ``other`` of self, return an

‎src/sage/schemes/elliptic_curves/hom.py

+105-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- :class:`~sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism`
1212
- :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite`
1313
- :class:`~sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar`
14+
- :class:`~sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius`
1415
- :class:`~sage.schemes.elliptic_curves.hom_velusqrt.EllipticCurveHom_velusqrt`
1516
1617
AUTHORS:
@@ -290,6 +291,7 @@ def kernel_polynomial(self):
290291
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.kernel_polynomial`
291292
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.kernel_polynomial`
292293
- :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.kernel_polynomial`
294+
- :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.kernel_polynomial`
293295
294296
TESTS::
295297
@@ -311,6 +313,7 @@ def dual(self):
311313
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.dual`
312314
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.dual`
313315
- :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.dual`
316+
- :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.dual`
314317
315318
TESTS::
316319
@@ -334,6 +337,7 @@ def rational_maps(self):
334337
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.rational_maps`
335338
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.rational_maps`
336339
- :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.rational_maps`
340+
- :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.rational_maps`
337341
338342
TESTS::
339343
@@ -356,6 +360,7 @@ def x_rational_map(self):
356360
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.x_rational_map`
357361
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.x_rational_map`
358362
- :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.x_rational_map`
363+
- :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.x_rational_map`
359364
360365
TESTS::
361366
@@ -436,15 +441,15 @@ def formal(self, prec=20):
436441
Eh = self._domain.formal()
437442
f, g = self.rational_maps()
438443
xh = Eh.x(prec=prec)
439-
assert xh.valuation() == -2, f"xh has valuation {xh.valuation()} (should be -2)"
444+
assert not self.is_separable() or xh.valuation() == -2, f"xh has valuation {xh.valuation()} (should be -2)"
440445
yh = Eh.y(prec=prec)
441-
assert yh.valuation() == -3, f"yh has valuation {yh.valuation()} (should be -3)"
446+
assert not self.is_separable() or yh.valuation() == -3, f"yh has valuation {yh.valuation()} (should be -3)"
442447
fh = f(xh,yh)
443-
assert fh.valuation() == -2, f"fh has valuation {fh.valuation()} (should be -2)"
448+
assert not self.is_separable() or fh.valuation() == -2, f"fh has valuation {fh.valuation()} (should be -2)"
444449
gh = g(xh,yh)
445-
assert gh.valuation() == -3, f"gh has valuation {gh.valuation()} (should be -3)"
450+
assert not self.is_separable() or gh.valuation() == -3, f"gh has valuation {gh.valuation()} (should be -3)"
446451
th = -fh/gh
447-
assert th.valuation() == +1, f"th has valuation {th.valuation()} (should be +1)"
452+
assert not self.is_separable() or th.valuation() == +1, f"th has valuation {th.valuation()} (should be +1)"
448453
return th
449454

450455
def is_normalized(self):
@@ -536,6 +541,7 @@ def is_separable(self):
536541
- :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.is_separable`
537542
- :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.is_separable`
538543
- :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.is_separable`
544+
- :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.is_separable`
539545
540546
TESTS::
541547
@@ -781,3 +787,97 @@ def compare_via_evaluation(left, right):
781787
assert False, "couldn't find a point of infinite order"
782788
else:
783789
raise NotImplementedError('not implemented for this base field')
790+
791+
792+
def find_post_isomorphism(phi, psi):
793+
r"""
794+
Given two isogenies `\phi: E\to E'` and `\psi: E\to E''`
795+
which are equal up to post-isomorphism defined over the
796+
same field, find that isomorphism.
797+
798+
In other words, this function computes an isomorphism
799+
`\alpha: E'\to E''` such that `\alpha\circ\phi = \psi`.
800+
801+
ALGORITHM:
802+
803+
Start with a list of all isomorphisms `E'\to E''`. Then
804+
repeatedly evaluate `\phi` and `\psi` at random points
805+
`P` to filter the list for isomorphisms `\alpha` with
806+
`\alpha(\phi(P)) = \psi(P)`. Once only one candidate is
807+
left, return it. Periodically extend the base field to
808+
avoid getting stuck (say, if all candidate isomorphisms
809+
act the same on all rational points).
810+
811+
EXAMPLES::
812+
813+
sage: from sage.schemes.elliptic_curves.hom import find_post_isomorphism
814+
sage: E = EllipticCurve(GF(7^2), [1,0])
815+
sage: f = E.scalar_multiplication(1)
816+
sage: g = choice(E.automorphisms())
817+
sage: find_post_isomorphism(f, g) == g
818+
True
819+
820+
::
821+
822+
sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
823+
sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
824+
sage: F.<i> = GF(883^2, modulus=x^2+1)
825+
sage: E = EllipticCurve(F, [1,0])
826+
sage: P = E.lift_x(117)
827+
sage: Q = E.lift_x(774)
828+
sage: w = WeierstrassIsomorphism(E, [i,0,0,0])
829+
sage: phi = EllipticCurveHom_composite(E, [P,w(Q)]) * w
830+
sage: psi = EllipticCurveHom_composite(E, [Q,w(P)])
831+
sage: phi.kernel_polynomial() == psi.kernel_polynomial()
832+
True
833+
sage: find_post_isomorphism(phi, psi)
834+
Elliptic-curve morphism:
835+
From: Elliptic Curve defined by y^2 = x^3 + 320*x + 482 over Finite Field in i of size 883^2
836+
To: Elliptic Curve defined by y^2 = x^3 + 320*x + 401 over Finite Field in i of size 883^2
837+
Via: (u,r,s,t) = (882*i, 0, 0, 0)
838+
"""
839+
E = phi.domain()
840+
if psi.domain() != E:
841+
raise ValueError('domains do not match')
842+
843+
isos = phi.codomain().isomorphisms(psi.codomain())
844+
if not isos:
845+
raise ValueError('codomains not isomorphic')
846+
847+
F = E.base_ring()
848+
from sage.rings.finite_rings import finite_field_base
849+
from sage.rings.number_field import number_field_base
850+
851+
if isinstance(F, finite_field_base.FiniteField):
852+
while len(isos) > 1:
853+
for _ in range(20):
854+
P = E.random_point()
855+
im_phi, im_psi = (phi._eval(P), psi._eval(P))
856+
isos = [iso for iso in isos if iso._eval(im_phi) == im_psi]
857+
if len(isos) <= 1:
858+
break
859+
else:
860+
E = E.base_extend(E.base_field().extension(2))
861+
862+
elif isinstance(F, number_field_base.NumberField):
863+
for _ in range(100):
864+
P = E.lift_x(F.random_element(), extend=True)
865+
if P.has_finite_order():
866+
continue
867+
break
868+
else:
869+
assert False, "couldn't find a point of infinite order"
870+
im_phi, im_psi = (phi._eval(P), psi._eval(P))
871+
isos = [iso for iso in isos if iso._eval(im_phi) == im_psi]
872+
873+
else:
874+
# fall back to generic method
875+
sc = psi.scaling_factor() / phi.scaling_factor()
876+
isos = [iso for iso in isos if iso.u == sc]
877+
878+
assert len(isos) <= 1
879+
if isos:
880+
return isos[0]
881+
882+
# found no suitable isomorphism -- either doesn't exist or a bug
883+
raise ValueError('isogenies not equal up to post-isomorphism')

‎src/sage/schemes/elliptic_curves/hom_frobenius.py

+538
Large diffs are not rendered by default.

‎src/sage/schemes/elliptic_curves/weierstrass_morphism.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,11 @@ def _composition_impl(left, right):
680680
We should return ``NotImplemented`` when passed a combination of
681681
elliptic-curve morphism types that we don't handle here::
682682
683-
sage: E = EllipticCurve([1,0])
684-
sage: phi = E.isogeny(E(0,0))
685-
sage: w1._composition_impl(phi.dual(), phi)
683+
sage: E1 = EllipticCurve([1,0])
684+
sage: phi = E1.isogeny(E1(0,0))
685+
sage: E2 = phi.codomain()
686+
sage: psi = E2.isogeny(E2(0,0))
687+
sage: w1._composition_impl(psi, phi)
686688
NotImplemented
687689
"""
688690
if isinstance(left, WeierstrassIsomorphism) and isinstance(right, WeierstrassIsomorphism):

0 commit comments

Comments
 (0)
Please sign in to comment.