From 2d1c955d44cc99bc77bfd011239c9176122d8419 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Wed, 8 Sep 2021 16:43:18 +0800 Subject: [PATCH 1/4] make sure multiplication_by_m_isogeny picks the correct isomorphism --- .../schemes/elliptic_curves/ell_generic.py | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index a1a48d2397f..c8a98409fdb 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -2150,7 +2150,7 @@ def multiplication_by_m(self, m, x_only=False): def multiplication_by_m_isogeny(self, m): r""" Return the ``EllipticCurveIsogeny`` object associated to the - multiplication-by-`m` map on self. + multiplication-by-`m` map on this elliptic curve. The resulting isogeny will have the associated rational maps (i.e. those returned by @@ -2169,7 +2169,7 @@ def multiplication_by_m_isogeny(self, m): OUTPUT: - An ``EllipticCurveIsogeny`` object associated to the - multiplication-by-`m` map on self. + multiplication-by-`m` map on this elliptic curve. EXAMPLES:: @@ -2177,6 +2177,29 @@ def multiplication_by_m_isogeny(self, m): sage: E.multiplication_by_m_isogeny(7) Isogeny of degree 49 from Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field to Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field + TESTS: + + Tests for :trac:`32490`:: + + sage: E = EllipticCurve(QQbar, [1,0]) + sage: E.multiplication_by_m_isogeny(1).rational_maps() + (x, y) + + :: + + sage: E = EllipticCurve_from_j(GF(31337).random_element()) + sage: P = E.random_point() + sage: [E.multiplication_by_m_isogeny(m)(P) == m*P for m in (1,2,3,5,7,9)] + [True, True, True, True, True, True] + + :: + + sage: E = EllipticCurve('99.a1') + sage: E.multiplication_by_m_isogeny(5) + Isogeny of degree 25 from Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 17*x + 30 over Rational Field to Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 17*x + 30 over Rational Field + sage: E.multiplication_by_m_isogeny(2).rational_maps() + ((1/4*x^4 + 33/4*x^2 - 121/2*x + 363/4)/(x^3 - 3/4*x^2 - 33/2*x + 121/4), + (-1/256*x^7 + 1/128*x^6*y - 7/256*x^6 - 3/256*x^5*y - 105/256*x^5 - 165/256*x^4*y + 1255/256*x^4 + 605/128*x^3*y - 473/64*x^3 - 1815/128*x^2*y - 10527/256*x^2 + 2541/128*x*y + 4477/32*x - 1331/128*y - 30613/256)/(1/16*x^6 - 3/32*x^5 - 519/256*x^4 + 341/64*x^3 + 1815/128*x^2 - 3993/64*x + 14641/256)) """ mx, my = self.multiplication_by_m(m) @@ -2184,7 +2207,14 @@ def multiplication_by_m_isogeny(self, m): phi = self.isogeny(torsion_poly, codomain=self) phi._EllipticCurveIsogeny__initialize_rational_maps(precomputed_maps=(mx, my)) - return phi + # trac 32490: using codomain=self can give a wrong isomorphism + for aut in self.automorphisms(): + phi.set_post_isomorphism(aut) + if phi.rational_maps() == (mx, my): + return phi + phi.set_post_isomorphism(~aut) + + assert False, 'bug in multiplication_by_m_isogeny()' def isomorphism_to(self, other): """ From 9161ffaf512d6803cdf028aeee8b8f16c79f2a5c Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Fri, 10 Sep 2021 23:03:17 +0800 Subject: [PATCH 2/4] make WeierstrassIsomorphism behave like EllipticCurveIsogeny --- .../elliptic_curves/ell_curve_isogeny.py | 348 -------------- src/sage/schemes/elliptic_curves/hom.py | 441 ++++++++++++++++++ .../elliptic_curves/weierstrass_morphism.py | 240 ++++++++-- 3 files changed, 648 insertions(+), 381 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index b1e55419fec..93074303620 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -77,7 +77,6 @@ from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism, isomorphisms from sage.sets.set import Set -from sage.structure.richcmp import richcmp_not_equal, richcmp # # Private function for parsing input to determine the type of @@ -859,12 +858,8 @@ class EllipticCurveIsogeny(EllipticCurveHom): __degree = None - __separable = True # This class only implements separable isogenies (for now.) - __algorithm = None - __this_hash = None - __check = None # # pre isomorphism @@ -1174,103 +1169,6 @@ def __iter__(self): """ return iter(self.rational_maps()) - def __hash__(self): - r""" - Function that implements the hash ability of Isogeny objects. - - This hashes the underlying kernel polynomial so that equal - isogeny objects have the same hash value. Also, this hashes - the base field, and domain and codomain curves as well, so - that isogenies with the same kernel polynomial (over different - base fields / curves) hash to different values. - - EXAMPLES:: - - sage: E = EllipticCurve(QQ, [0,0,0,1,0]) - sage: phi_v = EllipticCurveIsogeny(E, E((0,0))) - sage: phi_k = EllipticCurveIsogeny(E, [0,1]) - sage: phi_k.__hash__() == phi_v.__hash__() - True - sage: E_F17 = EllipticCurve(GF(17), [0,0,0,1,1]) - sage: phi_p = EllipticCurveIsogeny(E_F17, E_F17([0,1])) - sage: phi_p.__hash__() == phi_v.__hash__() - False - - sage: E = EllipticCurve('49a3') - sage: R. = QQ[] - sage: EllipticCurveIsogeny(E,X^3-13*X^2-58*X+503,check=False) - Isogeny of degree 7 from Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 107*x + 552 over Rational Field to Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 5252*x - 178837 over Rational Field - - """ - - if self.__this_hash is not None: - return self.__this_hash - - ker_poly_list = self.__kernel_polynomial_list - - if ker_poly_list is None: - ker_poly_list = self.__init_kernel_polynomial() - - this_hash = 0 - - for a in ker_poly_list: - this_hash ^= hash(a) - - this_hash ^= hash(self.__E1) - this_hash ^= hash(self.__E2) - this_hash ^= hash(self.__base_field) - - self.__this_hash = this_hash - - return self.__this_hash - - def _richcmp_(self, other, op): - r""" - Compare :class:`EllipticCurveIsogeny` objects. - - ALGORITHM: - - This method compares domains, codomains, and :meth:`rational_maps`. - - EXAMPLES:: - - sage: E = EllipticCurve(QQ, [0,0,0,1,0]) - sage: phi_v = EllipticCurveIsogeny(E, E((0,0))) - sage: phi_k = EllipticCurveIsogeny(E, [0,1]) - sage: phi_k == phi_v - True - sage: E_F17 = EllipticCurve(GF(17), [0,0,0,1,0]) - sage: phi_p = EllipticCurveIsogeny(E_F17, [0,1]) - sage: phi_p == phi_v - False - sage: E = EllipticCurve('11a1') - sage: phi = E.isogeny(E(5,5)) - sage: phi == phi - True - sage: phi == -phi - False - sage: psi = E.isogeny(phi.kernel_polynomial()) - sage: phi == psi - True - sage: phi.dual() == psi.dual() - True - """ - # We cannot just compare kernel polynomials, as was done until - # Trac #11327, as then phi and -phi compare equal, and - # similarly with phi and any composition of phi with an - # automorphism of its codomain, or any post-isomorphism. - # Comparing domains, codomains and rational maps seems much - # safer. - lx = self.domain() - rx = other.domain() - if lx != rx: - return richcmp_not_equal(lx, rx, op) - lx = self.codomain() - rx = other.codomain() - if lx != rx: - return richcmp_not_equal(lx, rx, op) - return richcmp(self.rational_maps(), other.rational_maps(), op) - def __neg__(self): r""" Function to implement unary negation (-) operator on @@ -1377,16 +1275,6 @@ def __clear_cached_values(self): EXAMPLES:: - sage: F = GF(7) - sage: E = EllipticCurve(j=F(0)) - sage: phi = EllipticCurveIsogeny(E, [E((0,-1)), E((0,1))]) - sage: old_hash = hash(phi) - sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism - sage: phi.set_post_isomorphism(WeierstrassIsomorphism(phi.codomain(), (-1,2,-3,4))) - ... - sage: hash(phi) == old_hash - False - sage: R. = QQ[] sage: E = EllipticCurve(QQ, [0,0,0,1,0]) sage: phi = EllipticCurveIsogeny(E, x) @@ -1403,19 +1291,15 @@ def __clear_cached_values(self): sage: E = EllipticCurve(j=F(1728)) sage: f = x^5 + 43*x^4 + 97*x^3 + 81*x^2 + 42*x + 82 sage: phi = EllipticCurveIsogeny(E, f) - sage: old_hash = hash(phi) sage: old_ratl_maps = phi.rational_maps() sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: phi.set_post_isomorphism(WeierstrassIsomorphism(phi.codomain(), (-13,13,-13,13))) ... - sage: old_hash == hash(phi) - False sage: old_ratl_maps == phi.rational_maps() False sage: phi._EllipticCurveIsogeny__clear_cached_values() """ - self.__this_hash = None self.__rational_maps_initialized = False self.__X_coord_rational_map = None self.__Y_coord_rational_map = None @@ -2822,29 +2706,6 @@ def x_rational_map(self): self.__initialize_rational_maps() return self.__X_coord_rational_map - def is_separable(self): - r""" - Return whether or not this isogeny is separable. - - .. NOTE:: - - This function always returns ``True`` as currently this - class only implements separable isogenies. - - EXAMPLES:: - - sage: E = EllipticCurve(GF(17), [0,0,0,3,0]) - sage: phi = EllipticCurveIsogeny(E, E((0,0))) - sage: phi.is_separable() - True - - sage: E = EllipticCurve('11a1') - sage: phi = EllipticCurveIsogeny(E, E.torsion_points()) - sage: phi.is_separable() - True - """ - return self.__separable - def kernel_polynomial(self): r""" Return the kernel polynomial of this isogeny. @@ -3190,86 +3051,6 @@ def _switch_sign(self): """ self._set_post_isomorphism(WeierstrassIsomorphism(self.__E2, (-1,0,-self.__E2.a1(),-self.__E2.a3()))) - def is_normalized(self): - r""" - Return whether this isogeny is normalized. - - .. NOTE:: - - An isogeny `\varphi\colon E\to E_2` between two given - Weierstrass equations is said to be normalized if the - constant `c` is `1` in `\varphi*(\omega_2) = c\cdot\omega`, - where `\omega` and `omega_2` are the invariant - differentials on `E` and `E_2` corresponding to the given - equation. - - ALGORITHM: - - The method checks if the leading term of the formal series - associated to this isogeny equals 1. - - EXAMPLES:: - - sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism - sage: E = EllipticCurve(GF(7), [0,0,0,1,0]) - sage: R. = GF(7)[] - sage: phi = EllipticCurveIsogeny(E, x) - sage: phi.is_normalized() - True - sage: isom = WeierstrassIsomorphism(phi.codomain(), (3, 0, 0, 0)) - sage: phi = isom * phi - sage: phi.is_normalized() - False - sage: isom = WeierstrassIsomorphism(phi.codomain(), (5, 0, 0, 0)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - - sage: F = GF(2^5, 'alpha'); alpha = F.gen() - sage: E = EllipticCurve(F, [1,0,1,1,1]) - sage: R. = F[] - sage: phi = EllipticCurveIsogeny(E, x+1) - sage: isom = WeierstrassIsomorphism(phi.codomain(), (alpha, 0, 0, 0)) - sage: phi.is_normalized() - True - sage: phi = isom * phi - sage: phi.is_normalized() - False - sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/alpha, 0, 0, 0)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - - sage: E = EllipticCurve('11a1') - sage: R. = QQ[] - sage: f = x^3 - x^2 - 10*x - 79/4 - sage: phi = EllipticCurveIsogeny(E, f) - sage: isom = WeierstrassIsomorphism(phi.codomain(), (2, 0, 0, 0)) - sage: phi.is_normalized() - True - sage: phi = isom * phi - sage: phi.is_normalized() - False - sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/2, 0, 0, 0)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) - sage: phi = isom * phi - sage: phi.is_normalized() - True - """ - phi_formal = self.formal(prec=5) - return phi_formal[1] == 1 - def dual(self): r""" Return the isogeny dual to this isogeny. @@ -3434,56 +3215,6 @@ def dual(self): return phi_hat - def formal(self,prec=20): - r""" - Return the formal isogeny as a power series in the variable - `t=-x/y` on the domain curve. - - INPUT: - - - ``prec`` - (default = 20), the precision with which the - computations in the formal group are carried out. - - EXAMPLES:: - - sage: E = EllipticCurve(GF(13),[1,7]) - sage: phi = E.isogeny(E(10,4)) - sage: phi.formal() - t + 12*t^13 + 2*t^17 + 8*t^19 + 2*t^21 + O(t^23) - - sage: E = EllipticCurve([0,1]) - sage: phi = E.isogeny(E(2,3)) - sage: phi.formal(prec=10) - t + 54*t^5 + 255*t^7 + 2430*t^9 + 19278*t^11 + O(t^13) - - sage: E = EllipticCurve('11a2') - sage: R. = QQ[] - sage: phi = E.isogeny(x^2 + 101*x + 12751/5) - sage: phi.formal(prec=7) - t - 2724/5*t^5 + 209046/5*t^7 - 4767/5*t^8 + 29200946/5*t^9 + O(t^10) - """ - Eh = self.__E1.formal() - f, g = self.rational_maps() - xh = Eh.x(prec=prec) - if xh.valuation() != -2: - raise RuntimeError("xh has valuation %s (should be -2)" % xh.valuation()) - yh = Eh.y(prec=prec) - if yh.valuation() != -3: - raise RuntimeError("yh has valuation %s (should be -3)" % yh.valuation()) - fh = f(xh,yh) - if fh.valuation() != -2: - raise RuntimeError("fh has valuation %s (should be -2)" % fh.valuation()) - gh = g(xh,yh) - if gh.valuation() != -3: - raise RuntimeError("gh has valuation %s (should be -3)" % gh.valuation()) - th = -fh/gh - if th.valuation() != 1: - raise RuntimeError("th has valuation %s (should be +1)" % th.valuation()) - return th - - # - # Overload Morphism methods that we want to - # @staticmethod def _composition_impl(left, right): @@ -3527,85 +3258,6 @@ def _composition_impl(left, right): return NotImplemented - def is_injective(self): - r""" - Return ``True`` if and only if this isogeny has trivial - kernel. - - EXAMPLES:: - - sage: E = EllipticCurve('11a1') - sage: R. = QQ[] - sage: f = x^2 + x - 29/5 - sage: phi = EllipticCurveIsogeny(E, f) - sage: phi.is_injective() - False - sage: phi = EllipticCurveIsogeny(E, R(1)) - sage: phi.is_injective() - True - - sage: F = GF(7) - sage: E = EllipticCurve(j=F(0)) - sage: phi = EllipticCurveIsogeny(E, [ E((0,-1)), E((0,1))]) - sage: phi.is_injective() - False - sage: phi = EllipticCurveIsogeny(E, E(0)) - sage: phi.is_injective() - True - """ - return not (1 < self.__degree) - - def is_surjective(self): - r""" - Return ``True`` if and only if this isogeny is surjective. - - .. NOTE:: - - This function always returns ``True``, as a non-constant - map of algebraic curves must be surjective, and this class - does not model the constant `0` isogeny. - - EXAMPLES:: - - sage: E = EllipticCurve('11a1') - sage: R. = QQ[] - sage: f = x^2 + x - 29/5 - sage: phi = EllipticCurveIsogeny(E, f) - sage: phi.is_surjective() - True - - sage: E = EllipticCurve(GF(7), [0,0,0,1,0]) - sage: phi = EllipticCurveIsogeny(E, E((0,0))) - sage: phi.is_surjective() - True - - sage: F = GF(2^5, 'omega') - sage: E = EllipticCurve(j=F(0)) - sage: R. = F[] - sage: phi = EllipticCurveIsogeny(E, x) - sage: phi.is_surjective() - True - """ - return True - - def is_zero(self): - r""" - Return whether this isogeny is zero. - - .. NOTE:: - - Currently this class does not allow zero isogenies, so this - function will always return True. - - EXAMPLES:: - - sage: E = EllipticCurve(j=GF(7)(0)) - sage: phi = EllipticCurveIsogeny(E, [ E((0,1)), E((0,-1))]) - sage: phi.is_zero() - False - """ - return self.degree().is_zero() - def n(self): r""" Numerical Approximation inherited from Map (through morphism), diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index 7083cfd1046..1e9c59c3ac7 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -8,10 +8,22 @@ EllipticCurveIsogeny class should inherit from this class in order to eventually provide a uniform interface for all elliptic-curve maps --- regardless of differences in internal representations. + +AUTHORS: + +- See authors of :class:`EllipticCurveIsogeny`. Some of the code + in this class was lifted from there. + +- Lorenz Panny (2021): Refactor isogenies and isomorphisms into + the common :class:`EllipticCurveHom` interface. """ +from sage.misc.cachefunc import cached_method +from sage.structure.richcmp import richcmp_not_equal, richcmp + from sage.categories.morphism import Morphism + class EllipticCurveHom(Morphism): """ Base class for elliptic-curve morphisms. @@ -59,3 +71,432 @@ def _composition_(self, other, homset): # fall back to generic formal composite map return Morphism._composition_(self, other, homset) + + def _richcmp_(self, other, op): + r""" + Compare :class:`EllipticCurveHom` objects. + + ALGORITHM: + + This method compares domains, codomains, and :meth:`rational_maps`. + + EXAMPLES:: + + sage: E = EllipticCurve(QQ, [0,0,0,1,0]) + sage: phi_v = EllipticCurveIsogeny(E, E((0,0))) + sage: phi_k = EllipticCurveIsogeny(E, [0,1]) + sage: phi_k == phi_v + True + sage: E_F17 = EllipticCurve(GF(17), [0,0,0,1,0]) + sage: phi_p = EllipticCurveIsogeny(E_F17, [0,1]) + sage: phi_p == phi_v + False + sage: E = EllipticCurve('11a1') + sage: phi = E.isogeny(E(5,5)) + sage: phi == phi + True + sage: phi == -phi + False + sage: psi = E.isogeny(phi.kernel_polynomial()) + sage: phi == psi + True + sage: phi.dual() == psi.dual() + True + """ + # We cannot just compare kernel polynomials, as was done until + # Trac #11327, as then phi and -phi compare equal, and + # similarly with phi and any composition of phi with an + # automorphism of its codomain, or any post-isomorphism. + # Comparing domains, codomains and rational maps seems much + # safer. + + lx, rx = self.domain(), other.domain() + if lx != rx: + return richcmp_not_equal(lx, rx, op) + + lx, rx = self.codomain(), other.codomain() + if lx != rx: + return richcmp_not_equal(lx, rx, op) + + lx, rx = self.degree(), other.degree() + if lx != rx: + return richcmp_not_equal(lx, rx, op) + + return richcmp(self.rational_maps(), other.rational_maps(), op) + + + def degree(self): + r""" + Return the degree of this elliptic-curve morphism. + + Implemented by child classes. For examples, + see :meth:`EllipticCurveIsogeny.degree` and + :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.degree`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom.degree(None) + Traceback (most recent call last): + ... + NotImplementedError: ... + """ + raise NotImplementedError('children must implement') + + def kernel_polynomial(self): + r""" + Return the kernel polynomial of this elliptic-curve morphism. + + Implemented by child classes. For examples, + see :meth:`EllipticCurveIsogeny.kernel_polynomial` and + :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.kernel_polynomial`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom.kernel_polynomial(None) + Traceback (most recent call last): + ... + NotImplementedError: ... + """ + raise NotImplementedError('children must implement') + + def dual(self): + r""" + Return the dual of this elliptic-curve morphism. + + Implemented by child classes. For examples, + see :meth:`EllipticCurveIsogeny.dual` and + :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.dual`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom.dual(None) + Traceback (most recent call last): + ... + NotImplementedError: ... + """ + raise NotImplementedError('children must implement') + + def rational_maps(self): + r""" + Return the pair of explicit rational maps defining this + elliptic-curve morphism as fractions of bivariate + polynomials in `x` and `y`. + + Implemented by child classes. For examples, + see :meth:`EllipticCurveIsogeny.rational_maps` and + :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.rational_maps`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom.rational_maps(None) + Traceback (most recent call last): + ... + NotImplementedError: ... + """ + raise NotImplementedError('children must implement') + + def x_rational_map(self): + r""" + Return the `x`-coordinate rational map of this elliptic-curve + morphism as a univariate rational expression in `x`. + + Implemented by child classes. For examples, + see :meth:`EllipticCurveIsogeny.x_rational_map` and + :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.x_rational_map`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom.x_rational_map(None) + Traceback (most recent call last): + ... + NotImplementedError: ... + """ + #TODO: could have a default implementation that simply + # returns the first component of rational_maps() + raise NotImplementedError('children must implement') + + + def formal(self, prec=20): + r""" + Return the formal isogeny associated to this elliptic-curve + morphism as a power series in the variable `t=-x/y` on the + domain curve. + + INPUT: + + - ``prec`` -- (default: 20), the precision with which the + computations in the formal group are carried out. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(13),[1,7]) + sage: phi = E.isogeny(E(10,4)) + sage: phi.formal() + t + 12*t^13 + 2*t^17 + 8*t^19 + 2*t^21 + O(t^23) + + :: + + sage: E = EllipticCurve([0,1]) + sage: phi = E.isogeny(E(2,3)) + sage: phi.formal(prec=10) + t + 54*t^5 + 255*t^7 + 2430*t^9 + 19278*t^11 + O(t^13) + + :: + + sage: E = EllipticCurve('11a2') + sage: R. = QQ[] + sage: phi = E.isogeny(x^2 + 101*x + 12751/5) + sage: phi.formal(prec=7) + t - 2724/5*t^5 + 209046/5*t^7 - 4767/5*t^8 + 29200946/5*t^9 + O(t^10) + """ + Eh = self._domain.formal() + f, g = self.rational_maps() + xh = Eh.x(prec=prec) + assert xh.valuation() == -2, f"xh has valuation {xh.valuation()} (should be -2)" + yh = Eh.y(prec=prec) + assert yh.valuation() == -3, f"yh has valuation {yh.valuation()} (should be -3)" + fh = f(xh,yh) + assert fh.valuation() == -2, f"fh has valuation {fh.valuation()} (should be -2)" + gh = g(xh,yh) + assert gh.valuation() == -3, f"gh has valuation {gh.valuation()} (should be -3)" + th = -fh/gh + assert th.valuation() == +1, f"th has valuation {th.valuation()} (should be +1)" + return th + + + def is_normalized(self): + r""" + Determine whether this morphism is a normalized isogeny. + + .. NOTE:: + + An isogeny `\varphi\colon E_1\to E_2` between two given + Weierstrass equations is said to be *normalized* if the + `\varphi^*(\omega_2) = \omega_1`, where `\omega_1` and + `\omega_2` are the invariant differentials on `E_1` and + `E_2` corresponding to the given equation. + + ALGORITHM: + + The method checks if the leading term of the formal series + associated to this isogeny equals `1`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve(GF(7), [0,0,0,1,0]) + sage: R. = GF(7)[] + sage: phi = EllipticCurveIsogeny(E, x) + sage: phi.is_normalized() + True + sage: isom = WeierstrassIsomorphism(phi.codomain(), (3, 0, 0, 0)) + sage: phi = isom * phi + sage: phi.is_normalized() + False + sage: isom = WeierstrassIsomorphism(phi.codomain(), (5, 0, 0, 0)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + + :: + + sage: F = GF(2^5, 'alpha'); alpha = F.gen() + sage: E = EllipticCurve(F, [1,0,1,1,1]) + sage: R. = F[] + sage: phi = EllipticCurveIsogeny(E, x+1) + sage: isom = WeierstrassIsomorphism(phi.codomain(), (alpha, 0, 0, 0)) + sage: phi.is_normalized() + True + sage: phi = isom * phi + sage: phi.is_normalized() + False + sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/alpha, 0, 0, 0)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + + :: + + sage: E = EllipticCurve('11a1') + sage: R. = QQ[] + sage: f = x^3 - x^2 - 10*x - 79/4 + sage: phi = EllipticCurveIsogeny(E, f) + sage: isom = WeierstrassIsomorphism(phi.codomain(), (2, 0, 0, 0)) + sage: phi.is_normalized() + True + sage: phi = isom * phi + sage: phi.is_normalized() + False + sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/2, 0, 0, 0)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1)) + sage: phi = isom * phi + sage: phi.is_normalized() + True + """ + phi_formal = self.formal(prec=5) + return phi_formal[1] == 1 + + + def is_separable(self): + r""" + Determine whether or not this morphism is separable. + + .. NOTE:: + + This method currently always returns ``True`` as Sage does + not yet implement inseparable isogenies. This will probably + change in the future. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(17), [0,0,0,3,0]) + sage: phi = EllipticCurveIsogeny(E, E((0,0))) + sage: phi.is_separable() + True + + :: + + sage: E = EllipticCurve('11a1') + sage: phi = EllipticCurveIsogeny(E, E.torsion_points()) + sage: phi.is_separable() + True + """ + return True + + def is_surjective(self): + r""" + Determine whether or not this morphism is surjective. + + .. NOTE:: + + This method currently always returns ``True``, since a + non-constant map of algebraic curves must be surjective, + and Sage does not yet implement the constant zero map. + This will probably change in the future. + + EXAMPLES:: + + sage: E = EllipticCurve('11a1') + sage: R. = QQ[] + sage: f = x^2 + x - 29/5 + sage: phi = EllipticCurveIsogeny(E, f) + sage: phi.is_surjective() + True + + :: + + sage: E = EllipticCurve(GF(7), [0,0,0,1,0]) + sage: phi = EllipticCurveIsogeny(E, E((0,0))) + sage: phi.is_surjective() + True + + :: + + sage: F = GF(2^5, 'omega') + sage: E = EllipticCurve(j=F(0)) + sage: R. = F[] + sage: phi = EllipticCurveIsogeny(E, x) + sage: phi.is_surjective() + True + """ + return True + + def is_injective(self): + r""" + Determine whether or not this morphism has trivial kernel. + + EXAMPLES:: + + sage: E = EllipticCurve('11a1') + sage: R. = QQ[] + sage: f = x^2 + x - 29/5 + sage: phi = EllipticCurveIsogeny(E, f) + sage: phi.is_injective() + False + sage: phi = EllipticCurveIsogeny(E, R(1)) + sage: phi.is_injective() + True + + :: + + sage: F = GF(7) + sage: E = EllipticCurve(j=F(0)) + sage: phi = EllipticCurveIsogeny(E, [ E((0,-1)), E((0,1))]) + sage: phi.is_injective() + False + sage: phi = EllipticCurveIsogeny(E, E(0)) + sage: phi.is_injective() + True + """ + # This will become wrong once purely inseparable isogenies + # are implemented. We should probably add something like a + # separable_degree() method then. + return self.degree() == 1 + + def is_zero(self): + """ + Check whether this elliptic-curve morphism is the zero map. + + .. NOTE:: + + This function currently always returns ``True`` as Sage + does not yet implement the constant zero morphism. This + will probably change in the future. + + EXAMPLES:: + + sage: E = EllipticCurve(j=GF(7)(0)) + sage: phi = EllipticCurveIsogeny(E, [E(0,1), E(0,-1)]) + sage: phi.is_zero() + False + """ + return not self.degree() + + @cached_method + def __hash__(self): + """ + Return a hash value for this elliptic-curve morphism. + + ALGORITHM: + + Hash a tuple containing the domain, codomain, and kernel + polynomial of this morphism. (The base field is factored + into the computation as part of the (co)domain hashes.) + + EXAMPLES:: + + sage: E = EllipticCurve(QQ, [0,0,0,1,0]) + sage: phi_v = EllipticCurveIsogeny(E, E((0,0))) + sage: phi_k = EllipticCurveIsogeny(E, [0,1]) + sage: phi_k.__hash__() == phi_v.__hash__() + True + sage: E_F17 = EllipticCurve(GF(17), [0,0,0,1,1]) + sage: phi_p = EllipticCurveIsogeny(E_F17, E_F17([0,1])) + sage: phi_p.__hash__() == phi_v.__hash__() + False + + :: + + sage: E = EllipticCurve('49a3') + sage: R. = QQ[] + sage: EllipticCurveIsogeny(E,X^3-13*X^2-58*X+503,check=False) + Isogeny of degree 7 from Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 107*x + 552 over Rational Field to Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 5252*x - 178837 over Rational Field + """ + return hash((self.domain(), self.codomain(), self.kernel_polynomial())) + diff --git a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py index f55123c70ea..41348896295 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -6,6 +6,7 @@ - Robert Bradshaw (2007): initial version - John Cremona (Jan 2008): isomorphisms, automorphisms and twists in all characteristics +- Lorenz Panny (2021): :class:`EllipticCurveHom` interface """ # **************************************************************************** # Copyright (C) 2007 Robert Bradshaw @@ -22,10 +23,13 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from sage.structure.element import get_coercion_model + from .constructor import EllipticCurve from sage.schemes.elliptic_curves.hom import EllipticCurveHom from sage.structure.richcmp import (richcmp_method, richcmp, richcmp_not_equal, op_NE) +from sage.rings.all import PolynomialRing @richcmp_method @@ -460,39 +464,30 @@ def __init__(self, E=None, urst=None, F=None): if F is None: # easy case baseWI.__init__(self, *urst) F = EllipticCurve(baseWI.__call__(self, list(E.a_invariants()))) - self._domain = E - self._codomain = F - EllipticCurveHom.__init__(self, self._domain, self._codomain) - return - if E is None: # easy case in reverse + elif E is None: # easy case in reverse baseWI.__init__(self, *urst) inv_urst = baseWI.__invert__(self) E = EllipticCurve(baseWI.__call__(inv_urst, list(F.a_invariants()))) - self._domain = E - self._codomain = F - EllipticCurveHom.__init__(self, self._domain, self._codomain) - return - if urst is None: # try to construct the morphism + elif urst is None: # try to construct the morphism urst = isomorphisms(E, F, True) if urst is None: raise ValueError("Elliptic curves not isomorphic.") baseWI.__init__(self, *urst) - self._domain = E - self._codomain = F - EllipticCurveHom.__init__(self, self._domain, self._codomain) - return - - # none of the parameters is None: - baseWI.__init__(self, *urst) - if F != EllipticCurve(baseWI.__call__(self, list(E.a_invariants()))): - raise ValueError("second argument is not an isomorphism from first argument to third argument") - else: - self._domain = E - self._codomain = F - EllipticCurveHom.__init__(self, self._domain, self._codomain) - return + + else: # none of the parameters is None: + baseWI.__init__(self, *urst) + if F != EllipticCurve(baseWI.__call__(self, list(E.a_invariants()))): + raise ValueError("second argument is not an isomorphism from first argument to third argument") + + base_ring = get_coercion_model().common_parent(E.base_ring(), F.base_ring(), *urst) + self._mpoly_ring = PolynomialRing(base_ring, ['x','y']) + self._poly_ring = PolynomialRing(base_ring, ['x']) + + self._domain = E + self._codomain = F + EllipticCurveHom.__init__(self, self._domain, self._codomain) def _richcmp_(self, other, op): r""" @@ -522,17 +517,20 @@ def _richcmp_(self, other, op): sage: a == c True """ - lx = self._domain - rx = other._domain - if lx != rx: - return richcmp_not_equal(lx, rx, op) + if isinstance(other, WeierstrassIsomorphism): + lx = self._domain + rx = other._domain + if lx != rx: + return richcmp_not_equal(lx, rx, op) + + lx = self._codomain + rx = other._codomain + if lx != rx: + return richcmp_not_equal(lx, rx, op) - lx = self._codomain - rx = other._codomain - if lx != rx: - return richcmp_not_equal(lx, rx, op) + return baseWI.__richcmp__(self, other, op) - return baseWI.__richcmp__(self, other, op) + return EllipticCurveHom._richcmp_(self, other, op) def __call__(self, P): r""" @@ -646,3 +644,179 @@ def __repr__(self): Via: (u,r,s,t) = (2, 3, 4, 5) """ return EllipticCurveHom.__repr__(self) + "\n Via: (u,r,s,t) = " + baseWI.__repr__(self) + + # EllipticCurveHom methods + + def degree(self): + """ + Return the degree as a rational map of this isomorphism. + + Isomorphisms always have degree `1` by definition. + + EXAMPLES:: + + sage: E1 = EllipticCurve([1,2,3,4,5]) + sage: E2 = EllipticCurve_from_j(E1.j_invariant()) + sage: E1.isomorphism_to(E2).degree() + 1 + """ + return 1 + + def rational_maps(self): + """ + Return the pair of rational maps defining this isomorphism. + + EXAMPLES:: + + sage: E1 = EllipticCurve([11,22,33,44,55]) + sage: E2 = EllipticCurve_from_j(E1.j_invariant()) + sage: iso = E1.isomorphism_to(E2); iso + Elliptic-curve morphism: + From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field + To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field + Via: (u,r,s,t) = (1, -17, -5, 77) + sage: iso.rational_maps() + (x + 17, 5*x + y + 8) + sage: f = E2.defining_polynomial()(*iso.rational_maps(), 1) + sage: I = E1.defining_ideal() + sage: x,y,z = I.ring().gens() + sage: f in I + Ideal(z-1) + True + + :: + + sage: E = EllipticCurve(GF(65537), [1,1,1,1,1]) + sage: w = E.isomorphism_to(E.short_weierstrass_model()) + sage: f,g = w.rational_maps() + sage: P = E.random_point() + sage: w(P).xy() == (f(P.xy()), g(P.xy())) + True + + TESTS:: + + sage: iso.rational_maps()[0].parent() + Multivariate Polynomial Ring in x, y over Rational Field + sage: iso.rational_maps()[1].parent() + Multivariate Polynomial Ring in x, y over Rational Field + """ + return tuple(baseWI.__call__(self, self._mpoly_ring.gens())) + + def x_rational_map(self): + """ + Return the `x`-coordinate rational map of this isomorphism. + + EXAMPLES:: + + sage: E1 = EllipticCurve([11,22,33,44,55]) + sage: E2 = EllipticCurve_from_j(E1.j_invariant()) + sage: iso = E1.isomorphism_to(E2); iso + Elliptic-curve morphism: + From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field + To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field + Via: (u,r,s,t) = (1, -17, -5, 77) + sage: iso.x_rational_map() + x + 17 + sage: iso.x_rational_map() == iso.rational_maps()[0] + True + + TESTS:: + + sage: iso.x_rational_map().parent() + Univariate Polynomial Ring in x over Rational Field + """ + x, = self._poly_ring.gens() + return (x - self.r) / self.u**2 + + def kernel_polynomial(self): + """ + Return the kernel polynomial of this isomorphism. + + Isomorphisms have trivial kernel by definition, hence this + method always returns `1`. + + EXAMPLES:: + + sage: E1 = EllipticCurve([11,22,33,44,55]) + sage: E2 = EllipticCurve_from_j(E1.j_invariant()) + sage: iso = E1.isomorphism_to(E2) + sage: iso.kernel_polynomial() + 1 + sage: psi = E1.isogeny(iso.kernel_polynomial(), codomain=E2); psi + Isogeny of degree 1 from Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field to Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field + sage: psi in {iso, -iso} + True + + TESTS:: + + sage: iso.kernel_polynomial().parent() + Univariate Polynomial Ring in x over Rational Field + """ + return self._poly_ring(1) + + def dual(self): + """ + Return the dual isogeny of this isomorphism. + + For isomorphisms, the dual is just the inverse. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve(QuadraticField(-3), [0,1]) + sage: w = WeierstrassIsomorphism(E, (CyclotomicField(3).gen(),0,0,0)) + sage: (w.dual() * w).rational_maps() + (x, y) + + :: + + sage: E1 = EllipticCurve([11,22,33,44,55]) + sage: E2 = E1.short_weierstrass_model() + sage: iso = E1.isomorphism_to(E2) + sage: iso.dual() == ~iso + True + """ + return ~self + + def __neg__(self): + """ + Return the negative of this isomorphism, i.e., its composition + with the negation map `[-1]`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve([11,22,33,44,55]) + sage: w = WeierstrassIsomorphism(E, (66,77,88,99)) + sage: -w + Elliptic-curve morphism: + From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field + To: Elliptic Curve defined by y^2 + 17/6*x*y + 49/13068*y = x^3 - 769/396*x^2 - 3397/862488*x + 44863/7513995456 over Rational Field + Via: (u,r,s,t) = (-66, 77, -99, -979) + sage: -(-w) == w + True + + :: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve(QuadraticField(-3), [0,1]) + sage: w = WeierstrassIsomorphism(E, (CyclotomicField(3).gen(),0,0,0)) + sage: w.tuple() + (zeta3, 0, 0, 0) + sage: (-w).tuple() + (-zeta3, 0, 0, 0) + sage: (-w)^3 == -(w^3) + True + + :: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve(QuadraticField(-1), [1,0]) + sage: t = WeierstrassIsomorphism(E, (i,0,0,0)) + sage: -t^2 == WeierstrassIsomorphism(E, (1,0,0,0)) + True + """ + a1,_,a3,_,_ = self._domain.a_invariants() + w = baseWI(-1, 0, -a1, -a3) + urst = baseWI.__mul__(self, w).tuple() + return WeierstrassIsomorphism(self._domain, urst, self._codomain) + From 440cff05ed54bc79b9d75e0ffe815490df3cfc53 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Fri, 10 Sep 2021 23:16:07 +0800 Subject: [PATCH 3/4] add test for comparisons between different EllipticCurveHom children --- src/sage/schemes/elliptic_curves/hom.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index 1e9c59c3ac7..bf218589f60 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -102,6 +102,20 @@ def _richcmp_(self, other, op): True sage: phi.dual() == psi.dual() True + + :: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: E = EllipticCurve([9,9]) + sage: F = E.change_ring(GF(71)) + sage: wE = WeierstrassIsomorphism(E, (1,0,0,0)) + sage: wF = WeierstrassIsomorphism(F, (1,0,0,0)) + sage: mE = E.multiplication_by_m_isogeny(1) + sage: mF = F.multiplication_by_m_isogeny(1) + sage: [mE == wE, mF == wF] + [True, True] + sage: [a == b for a in (wE,mE) for b in (wF,mF)] + [False, False, False, False] """ # We cannot just compare kernel polynomials, as was done until # Trac #11327, as then phi and -phi compare equal, and From 80b0bb11171daf79122859a83d0940bd3fd2423a Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Sat, 11 Sep 2021 11:23:08 +0800 Subject: [PATCH 4/4] reflect this change in the documentation --- src/sage/schemes/elliptic_curves/hom.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index bf218589f60..5c07ea4b41e 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -1,13 +1,14 @@ """ Elliptic-curve morphisms -This class serves as a common parent for various specializations -of morphisms between elliptic curves. In the future, some of the -code currently contained in the EllipticCurveIsogeny class should -be moved here, and new code for cases not currently covered by the -EllipticCurveIsogeny class should inherit from this class in order -to eventually provide a uniform interface for all elliptic-curve -maps --- regardless of differences in internal representations. +This class serves as a common parent for various specializations of +morphisms between elliptic curves, with the aim of providing a common +interface regardless of implementation details. + +Current implementations of elliptic-curve morphisms (child classes): + +- :class:`EllipticCurveIsogeny` +- :class:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism` AUTHORS: