diff --git a/src/doc/en/reference/arithmetic_curves/index.rst b/src/doc/en/reference/arithmetic_curves/index.rst index 6ab10dcf1c0..669a05b02a9 100644 --- a/src/doc/en/reference/arithmetic_curves/index.rst +++ b/src/doc/en/reference/arithmetic_curves/index.rst @@ -18,10 +18,11 @@ Maps between them :maxdepth: 1 sage/schemes/elliptic_curves/hom + sage/schemes/elliptic_curves/hom_composite + sage/schemes/elliptic_curves/hom_sum sage/schemes/elliptic_curves/weierstrass_morphism sage/schemes/elliptic_curves/ell_curve_isogeny sage/schemes/elliptic_curves/hom_velusqrt - sage/schemes/elliptic_curves/hom_composite sage/schemes/elliptic_curves/hom_scalar sage/schemes/elliptic_curves/hom_frobenius sage/schemes/elliptic_curves/isogeny_small_degree diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 75d10d147d6..1ab3981b4cf 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -2879,28 +2879,19 @@ def kernel_polynomial(self): self.__init_kernel_polynomial() return self.__kernel_polynomial - def is_separable(self): + def inseparable_degree(self): r""" - Determine whether or not this isogeny is separable. + Return the inseparable degree of this isogeny. - Since :class:`EllipticCurveIsogeny` only implements - separable isogenies, this method always returns ``True``. + Since this class only implements separable isogenies, + this method always returns one. - EXAMPLES:: - - sage: E = EllipticCurve(GF(17), [0,0,0,3,0]) - sage: phi = EllipticCurveIsogeny(E, E((0,0))) - sage: phi.is_separable() - True - - :: + TESTS:: - sage: E = EllipticCurve('11a1') - sage: phi = EllipticCurveIsogeny(E, E.torsion_points()) - sage: phi.is_separable() - True + sage: EllipticCurveIsogeny.inseparable_degree(None) + 1 """ - return True + return Integer(1) def _set_pre_isomorphism(self, preWI): """ diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 7dd65c95749..d29ae37c1ae 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -2049,13 +2049,13 @@ def compute_model(E, name): raise NotImplementedError(f'cannot compute {name} model') -def point_of_order(E, l): +def point_of_order(E, n): r""" Given an elliptic curve `E` over a finite field or a number field - and an integer `\ell \geq 1`, construct a point of order `\ell` on `E`, + and an integer `n \geq 1`, construct a point of order `n` on `E`, possibly defined over an extension of the base field of `E`. - Currently only prime values of `\ell` are supported. + Currently only prime powers `n` are supported. EXAMPLES:: @@ -2070,6 +2070,13 @@ def point_of_order(E, l): sage: P.curve().a_invariants() (1, 2, 3, 4, 5) + :: + + sage: Q = point_of_order(E, 8); Q + (69*x^5 + 24*x^4 + 100*x^3 + 65*x^2 + 88*x + 97 : 65*x^5 + 28*x^4 + 5*x^3 + 45*x^2 + 42*x + 18 : 1) + sage: 8*Q == 0 and 4*Q != 0 + True + :: sage: from sage.schemes.elliptic_curves.ell_field import point_of_order @@ -2078,10 +2085,23 @@ def point_of_order(E, l): (x : -Y : 1) sage: P.base_ring() Number Field in Y with defining polynomial Y^2 - x^3 - 7*x - 7 over its base field + sage: P.base_ring().base_field() + Number Field in x with defining polynomial x^4 + 14*x^2 + 28*x - 49/3 sage: P.order() 3 sage: P.curve().a_invariants() (0, 0, 0, 7, 7) + + :: + + sage: Q = point_of_order(E, 4); Q # random + (x : Y : 1) + sage: Q.base_ring() + Number Field in Y with defining polynomial Y^2 - x^3 - 7*x - 7 over its base field + sage: Q.base_ring().base_field() + Number Field in x with defining polynomial x^6 + 35*x^4 + 140*x^3 - 245*x^2 - 196*x - 735 + sage: Q.order() + 4 """ # Construct the field extension defined by the given polynomial, # in such a way that the result is recognized by Sage as a field. @@ -2094,14 +2114,16 @@ def ffext(poly): return poly.splitting_field(rng.variable_name()) return fld.extension(poly, rng.variable_name()) - l = ZZ(l) - if l == 1: + n = ZZ(n) + if n == 1: return E(0) - if not l.is_prime(): - raise NotImplementedError('composite orders are currently unsupported') + l,m = n.is_prime_power(get_data=True) + if not m: + raise NotImplementedError('only prime-power orders are currently supported') - xpoly = E.division_polynomial(l) + xpoly = E.division_polynomial(n).radical() + xpoly //= E.division_polynomial(n//l).radical() if xpoly.degree() < 1: # supersingular and l == p raise ValueError('curve does not have any points of the specified order') @@ -2116,4 +2138,6 @@ def ffext(poly): xx = FF(xx) EE = E.change_ring(FF) - return EE.lift_x(xx) + pt = EE.lift_x(xx) + pt.set_order(n, check=False) + return pt diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 1e760dcfd6c..d35790a13f3 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -1359,9 +1359,10 @@ def set_order(self, value=None, *, multiple=None, check=True): raise ValueError('Value %s illegal for point order' % value) E = self.curve() q = E.base_ring().cardinality() - low, hi = Hasse_bounds(q) - if value > hi: - raise ValueError('Value %s illegal: outside max Hasse bound' % value) + if q < oo: + _, hi = Hasse_bounds(q) + if value > hi: + raise ValueError('Value %s illegal: outside max Hasse bound' % value) if value * self != E(0): raise ValueError('Value %s illegal: %s * %s is not the identity' % (value, value, self)) if hasattr(self, '_order') and self._order != value: # already known diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index 995420ae438..fb7855993ea 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -10,6 +10,7 @@ - :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` - :class:`~sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism` - :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite` +- :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_sum` - :class:`~sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar` - :class:`~sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius` - :class:`~sage.schemes.elliptic_curves.hom_velusqrt.EllipticCurveHom_velusqrt` @@ -134,6 +135,51 @@ def _composition_(self, other, homset): from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite return EllipticCurveHom_composite.from_factors([other, self]) + def _add_(self, other): + r""" + Add two :class:`EllipticCurveHom` objects by constructing a + formal :class:`EllipticCurveHom_sum`. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: phi + phi # indirect doctest + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101) + """ + from sage.schemes.elliptic_curves.hom_sum import EllipticCurveHom_sum + phis = [] + if isinstance(self, EllipticCurveHom_sum): + phis += self.summands() + else: + phis.append(self) + if isinstance(other, EllipticCurveHom_sum): + phis += other.summands() + else: + phis.append(other) + #TODO should probably try to simplify some more? + return EllipticCurveHom_sum(phis) + + def _sub_(self, other): + r""" + Subtract two :class:`EllipticCurveHom` objects by negating + and constructing a formal :class:`EllipticCurveHom_sum`. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: phi - phi # indirect doctest + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101) + """ + return self + (-other) + @staticmethod def _comparison_impl(left, right, op): r""" @@ -384,6 +430,7 @@ def kernel_polynomial(self): - :meth:`EllipticCurveIsogeny.kernel_polynomial` - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.kernel_polynomial` - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.kernel_polynomial` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.kernel_polynomial` - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.kernel_polynomial` - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.kernel_polynomial` @@ -406,6 +453,7 @@ def dual(self): - :meth:`EllipticCurveIsogeny.dual` - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.dual` - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.dual` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.dual` - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.dual` - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.dual` @@ -430,6 +478,7 @@ def rational_maps(self): - :meth:`EllipticCurveIsogeny.rational_maps` - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.rational_maps` - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.rational_maps` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.rational_maps` - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.rational_maps` - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.rational_maps` @@ -453,6 +502,7 @@ def x_rational_map(self): - :meth:`EllipticCurveIsogeny.x_rational_map` - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.x_rational_map` - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.x_rational_map` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.x_rational_map` - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.x_rational_map` - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.x_rational_map` @@ -484,6 +534,7 @@ def scaling_factor(self): - :meth:`EllipticCurveIsogeny.scaling_factor` - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.scaling_factor` - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.scaling_factor` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.scaling_factor` - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.scaling_factor` TESTS:: @@ -623,40 +674,137 @@ def is_normalized(self): ALGORITHM: We check if :meth:`scaling_factor` returns `1`. """ - return self.scaling_factor() == 1 + return self.scaling_factor().is_one() - def is_separable(self): + def inseparable_degree(self): r""" - Determine whether or not this morphism is separable. + Return the inseparable degree of this isogeny. Implemented by child classes. For examples, see: - - :meth:`EllipticCurveIsogeny.is_separable` - - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.is_separable` - - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.is_separable` - - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.is_separable` - - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.is_separable` + - :meth:`EllipticCurveIsogeny.inseparable_degree` + - :meth:`sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism.inseparable_degree` + - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite.inseparable_degree` + - :meth:`sage.schemes.elliptic_curves.hom_sum.EllipticCurveHom_sum.inseparable_degree` + - :meth:`sage.schemes.elliptic_curves.hom_scalar.EllipticCurveHom_scalar.inseparable_degree` + - :meth:`sage.schemes.elliptic_curves.hom_frobenius.EllipticCurveHom_frobenius.inseparable_degree` TESTS:: sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom - sage: EllipticCurveHom.is_separable(None) + sage: EllipticCurveHom.inseparable_degree(None) Traceback (most recent call last): ... NotImplementedError: ... """ raise NotImplementedError('children must implement') - def is_surjective(self): + def separable_degree(self): r""" - Determine whether or not this morphism is surjective. + Return the separable degree of this isogeny. - .. NOTE:: + The separable degree is the result of dividing the :meth:`degree` + by the :meth:`inseparable_degree`. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(11), [5,5]) + sage: E.is_supersingular() + False + sage: E.scalar_multiplication(-77).separable_degree() + 539 + sage: E = EllipticCurve(GF(11), [5,0]) + sage: E.is_supersingular() + True + sage: E.scalar_multiplication(-77).separable_degree() + 49 + """ + return self.degree() // self.inseparable_degree() + + def is_separable(self): + r""" + Determine whether or not this morphism is a separable isogeny. + + 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 + + :: + + sage: E = EllipticCurve(GF(31337), [0,1]) # needs sage.rings.finite_rings + sage: {f.is_separable() for f in E.automorphisms()} # needs sage.rings.finite_rings + {True} + + :: + + sage: # needs sage.rings.finite_rings + sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + sage: E = EllipticCurve(GF(7^2), [3,2]) + sage: P = E.lift_x(1) + sage: phi = EllipticCurveHom_composite(E, P); phi + Composite morphism of degree 7 = 7: + From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 + over Finite Field in z2 of size 7^2 + To: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 + over Finite Field in z2 of size 7^2 + sage: phi.is_separable() + True + + :: + + sage: E = EllipticCurve(GF(11), [4,4]) + sage: E.scalar_multiplication(11).is_separable() + False + sage: E.scalar_multiplication(-11).is_separable() + False + sage: E.scalar_multiplication(777).is_separable() + True + sage: E.scalar_multiplication(-1).is_separable() + True + sage: E.scalar_multiplication(77).is_separable() + False + sage: E.scalar_multiplication(121).is_separable() + False + + :: + + sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius + sage: E = EllipticCurve(GF(11), [1,1]) + sage: pi = EllipticCurveHom_frobenius(E) + sage: pi.degree() + 11 + sage: pi.is_separable() + False + sage: pi = EllipticCurveHom_frobenius(E, 0) + sage: pi.degree() + 1 + sage: pi.is_separable() + True + + :: + + sage: E = EllipticCurve(GF(17), [0,0,0,3,0]) + sage: phi = E.isogeny(E((1,2)), algorithm='velusqrt') + sage: phi.is_separable() + True + """ + if self.is_zero(): + raise ValueError('constant zero map is not an isogeny') + return self.inseparable_degree().is_one() - 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. + def is_surjective(self): + r""" + Determine whether or not this morphism is surjective. EXAMPLES:: @@ -689,6 +837,9 @@ def is_injective(self): r""" Determine whether or not this morphism has trivial kernel. + The kernel is trivial if and only if this morphism is a + purely inseparable isogeny. + EXAMPLES:: sage: E = EllipticCurve('11a1') @@ -711,22 +862,59 @@ def is_injective(self): sage: phi = EllipticCurveIsogeny(E, E(0)) sage: phi.is_injective() True + + :: + + sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + sage: E = EllipticCurve([1,0]) + sage: phi = EllipticCurveHom_composite(E, E(0,0)) + sage: phi.is_injective() + False + sage: E = EllipticCurve_from_j(GF(3).algebraic_closure()(0)) + sage: nu = EllipticCurveHom_composite.from_factors(E.automorphisms()) + sage: nu + Composite morphism of degree 1 = 1^12: + From: Elliptic Curve defined by y^2 = x^3 + x + over Algebraic closure of Finite Field of size 3 + To: Elliptic Curve defined by y^2 = x^3 + x + over Algebraic closure of Finite Field of size 3 + sage: nu.is_injective() + True + + :: + + sage: E = EllipticCurve(GF(23), [1,0]) + sage: E.scalar_multiplication(4).is_injective() + False + sage: E.scalar_multiplication(5).is_injective() + False + sage: E.scalar_multiplication(1).is_injective() + True + sage: E.scalar_multiplication(-1).is_injective() + True + sage: E.scalar_multiplication(23).is_injective() + True + sage: E.scalar_multiplication(-23).is_injective() + True + sage: E.scalar_multiplication(0).is_injective() + False + + :: + + sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius + sage: E = EllipticCurve(GF(11), [1,1]) + sage: pi = EllipticCurveHom_frobenius(E, 5) + sage: pi.is_injective() + True """ - if not self.is_separable(): - # TODO: should implement .separable_degree() or similar - raise NotImplementedError - return self.degree() == 1 + if self.is_zero(): + return False + return self.separable_degree().is_one() def is_zero(self): r""" 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)) @@ -926,11 +1114,11 @@ def matrix_on_subgroup(self, domain_gens, codomain_gens=None): if R.weil_pairing(S, n).multiplicative_order() != n: raise ValueError('generator points on codomain are not independent') - imP = self(P) - imQ = self(Q) + imP = self._eval(P) + imQ = self._eval(Q) from sage.groups.additive_abelian.additive_abelian_wrapper import AdditiveAbelianGroupWrapper - H = AdditiveAbelianGroupWrapper(self.codomain().point_homset(), [R,S], [n,n]) + H = AdditiveAbelianGroupWrapper(R.parent(), [R,S], [n,n]) vecP = H.discrete_log(imP) vecQ = H.discrete_log(imQ) diff --git a/src/sage/schemes/elliptic_curves/hom_composite.py b/src/sage/schemes/elliptic_curves/hom_composite.py index e457c9854ee..ed87349bdcd 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -814,30 +814,6 @@ def dual(self): phis = (phi.dual() for phi in self._phis[::-1]) return EllipticCurveHom_composite.from_factors(phis) - def is_separable(self): - """ - Determine whether this composite isogeny is separable. - - A composition of isogenies is separable if and only if - all factors are. - - EXAMPLES:: - - sage: # needs sage.rings.finite_rings - sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite - sage: E = EllipticCurve(GF(7^2), [3,2]) - sage: P = E.lift_x(1) - sage: phi = EllipticCurveHom_composite(E, P); phi - Composite morphism of degree 7 = 7: - From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 - over Finite Field in z2 of size 7^2 - To: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 - over Finite Field in z2 of size 7^2 - sage: phi.is_separable() - True - """ - return all(phi.is_separable() for phi in self._phis) - def formal(self, prec=20): """ Return the formal isogeny corresponding to this composite @@ -899,29 +875,21 @@ def scaling_factor(self): """ return prod(phi.scaling_factor() for phi in self._phis) - def is_injective(self): - """ - Determine whether this composite morphism has trivial kernel. + def inseparable_degree(self): + r""" + Return the inseparable degree of this morphism. - In other words, return ``True`` if and only if ``self`` is a - purely inseparable isogeny. + Like the degree, the inseparable degree is multiplicative + under composition, so this method returns the product of + the inseparable degrees of the factors. EXAMPLES:: - sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite - sage: E = EllipticCurve([1,0]) - sage: phi = EllipticCurveHom_composite(E, E(0,0)) - sage: phi.is_injective() - False - sage: E = EllipticCurve_from_j(GF(3).algebraic_closure()(0)) - sage: nu = EllipticCurveHom_composite.from_factors(E.automorphisms()) - sage: nu - Composite morphism of degree 1 = 1^12: - From: Elliptic Curve defined by y^2 = x^3 + x - over Algebraic closure of Finite Field of size 3 - To: Elliptic Curve defined by y^2 = x^3 + x - over Algebraic closure of Finite Field of size 3 - sage: nu.is_injective() - True + sage: E = EllipticCurve(j=GF(11^5).random_element()) + sage: phi = E.frobenius_isogeny(2) * E.scalar_multiplication(77) + sage: type(phi) + + sage: phi.inseparable_degree() + 1331 """ - return all(phi.is_injective() for phi in self._phis) + return prod(phi.inseparable_degree() for phi in self._phis) diff --git a/src/sage/schemes/elliptic_curves/hom_frobenius.py b/src/sage/schemes/elliptic_curves/hom_frobenius.py index fb4496aedbb..0c1254e15b0 100644 --- a/src/sage/schemes/elliptic_curves/hom_frobenius.py +++ b/src/sage/schemes/elliptic_curves/hom_frobenius.py @@ -309,20 +309,6 @@ def _repr_(self): # EllipticCurveHom methods - def degree(self): - """ - Return the degree of this Frobenius isogeny. - - EXAMPLES:: - - sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius - sage: E = EllipticCurve(GF(11), [1,1]) - sage: pi = EllipticCurveHom_frobenius(E, 4) - sage: pi.degree() - 14641 - """ - return self._degree - def rational_maps(self): """ Return the explicit rational maps defining this Frobenius @@ -515,44 +501,21 @@ def dual(self): iso = find_post_isomorphism(Phi * self, scalar_mul) return iso * Phi - def is_separable(self): - """ - Determine whether or not this Frobenius isogeny is separable. - - Since Frobenius isogenies are purely inseparable, this method - returns ``True`` if and only if the degree is `1`. - - EXAMPLES:: - - sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius - sage: E = EllipticCurve(GF(11), [1,1]) - sage: pi = EllipticCurveHom_frobenius(E) - sage: pi.degree() - 11 - sage: pi.is_separable() - False - sage: pi = EllipticCurveHom_frobenius(E, 0) - sage: pi.degree() - 1 - sage: pi.is_separable() - True - """ - return self._degree == 1 - - def is_injective(self): + def inseparable_degree(self): """ - Determine whether or not this Frobenius isogeny has trivial - kernel. + Return the inseparable degree of this Frobenius isogeny. - Since Frobenius isogenies are purely inseparable, this method - always returns ``True``. + Since this class implements only purely inseparable isogenies, + the inseparable degree equals the degree. EXAMPLES:: sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius sage: E = EllipticCurve(GF(11), [1,1]) - sage: pi = EllipticCurveHom_frobenius(E, 5) - sage: pi.is_injective() + sage: pi = EllipticCurveHom_frobenius(E, 4) + sage: pi.inseparable_degree() + 14641 + sage: pi.inseparable_degree() == pi.degree() True """ - return True + return self._degree diff --git a/src/sage/schemes/elliptic_curves/hom_scalar.py b/src/sage/schemes/elliptic_curves/hom_scalar.py index 66e675250f9..03f042fd830 100644 --- a/src/sage/schemes/elliptic_curves/hom_scalar.py +++ b/src/sage/schemes/elliptic_curves/hom_scalar.py @@ -452,69 +452,38 @@ def dual(self): """ return self - def is_separable(self): - """ - Determine whether this scalar-multiplication map is a - separable isogeny. (This is the case if and only if the - scalar `m` is coprime to the characteristic.) + def inseparable_degree(self): + r""" + Return the inseparable degree of this scalar-multiplication map. EXAMPLES:: - sage: E = EllipticCurve(GF(11), [4,4]) - sage: E.scalar_multiplication(11).is_separable() - False - sage: E.scalar_multiplication(-11).is_separable() - False - sage: E.scalar_multiplication(777).is_separable() - True - sage: E.scalar_multiplication(-1).is_separable() - True - sage: E.scalar_multiplication(77).is_separable() + sage: E = EllipticCurve(GF(7), [0,1]) + sage: E.is_supersingular() False - sage: E.scalar_multiplication(121).is_separable() - False - - TESTS:: - - sage: E.scalar_multiplication(0).is_separable() - Traceback (most recent call last): - ... - ValueError: [0] is not an isogeny - """ - if self._m.is_zero(): - raise ValueError('[0] is not an isogeny') - return bool(self.scaling_factor()) + sage: E.scalar_multiplication(4).inseparable_degree() + 1 + sage: E.scalar_multiplication(-7).inseparable_degree() + 7 - def is_injective(self): - """ - Determine whether this scalar multiplication defines an - injective map (over the algebraic closure). - - Equivalently, return ``True`` if and only if this scalar - multiplication is a purely inseparable isogeny. - - EXAMPLES:: + :: - sage: E = EllipticCurve(GF(23), [1,0]) - sage: E.scalar_multiplication(4).is_injective() - False - sage: E.scalar_multiplication(5).is_injective() - False - sage: E.scalar_multiplication(1).is_injective() - True - sage: E.scalar_multiplication(-1).is_injective() - True - sage: E.scalar_multiplication(23).is_injective() + sage: E = EllipticCurve(GF(7), [1,0]) + sage: E.is_supersingular() True - sage: E.scalar_multiplication(-23).is_injective() - True - sage: E.scalar_multiplication(0).is_injective() - False + sage: E.scalar_multiplication(4).inseparable_degree() + 1 + sage: E.scalar_multiplication(-7).inseparable_degree() + 49 """ - if self._m.is_zero(): - return False - p = self._domain.base_ring().characteristic() - return self._m.abs().is_power_of(p) and self._domain.is_supersingular() + p = self.base_ring().characteristic() + if not p: + return ZZ.one() + v = self._m.valuation(p) + if not v: + return ZZ.one() + rk = 1 + self._domain.is_supersingular() + return p**(rk*v) def __neg__(self): """ diff --git a/src/sage/schemes/elliptic_curves/hom_sum.py b/src/sage/schemes/elliptic_curves/hom_sum.py new file mode 100644 index 00000000000..6660035634d --- /dev/null +++ b/src/sage/schemes/elliptic_curves/hom_sum.py @@ -0,0 +1,676 @@ +r""" +Sums of morphisms of elliptic curves + +The set `\mathrm{Hom}(E,E')` of morphisms between two elliptic curves +forms an abelian group under pointwise addition. An important special +case is the endomorphism ring `\mathrm{End}(E) = \mathrm{Hom}(E,E)`. +However, it is not immediately obvious how to compute some properties +of the sum `\varphi+\psi` of two isogenies, even when both are given +explicitly. This class provides functionality for representing sums of +elliptic-curve morphisms (in particular, isogenies and endomorphisms) +formally, and explicitly computing important properties (such as the +degree or the kernel polynomial) from the formal representation. + +EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: phi + phi + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101) + sage: phi + phi == phi * E.scalar_multiplication(2) + True + sage: phi + phi + phi == phi * E.scalar_multiplication(3) + True + +An example of computing with a supersingular endomorphism ring:: + + sage: E = EllipticCurve(GF(419^2), [1,0]) + sage: i = E.automorphisms()[-1] + sage: j = E.frobenius_isogeny() + sage: i * j == - j * i # i,j anticommute + True + sage: (i + j) * i == i^2 - i*j # distributive law + True + sage: (j - E.scalar_multiplication(1)).degree() # point counting! + 420 + +AUTHORS: + +- Lorenz Panny (2023) +""" + +from sage.misc.cachefunc import cached_method +from sage.structure.sequence import Sequence + +from sage.arith.misc import gcd + +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring import polygen + +from sage.sets.primes import Primes + +from sage.schemes.elliptic_curves.ell_field import point_of_order +from sage.groups.generic import discrete_log, order_from_multiple + +from sage.schemes.elliptic_curves.hom import EllipticCurveHom, compare_via_evaluation + + +class EllipticCurveHom_sum(EllipticCurveHom): + + _degree = None + _phis = None + + def __init__(self, phis, domain=None, codomain=None): + r""" + Construct a sum morphism of elliptic curves from its summands. + (For empty sums, the domain and codomain curves must be given.) + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_sum import EllipticCurveHom_sum + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: EllipticCurveHom_sum([phi, phi]) + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101) + + The zero morphism can be constructed even between non-isogenous curves:: + + sage: E1 = EllipticCurve(GF(101), [5,5]) + sage: E2 = EllipticCurve(GF(101), [7,7]) + sage: E1.is_isogenous(E2) + False + sage: EllipticCurveHom_sum([], E1, E2) + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 7*x + 7 over Finite Field of size 101 + Via: () + """ + phis = tuple(phis) + + if not phis and (domain is None or codomain is None): + raise ValueError('need either phis or both domain and codomain') + + for phi in phis: + if not isinstance(phi, EllipticCurveHom): + raise ValueError(f'not an elliptic-curve morphism: {phi}') + + if domain is None: + domain = phis[0].domain() + if codomain is None: + codomain = phis[0].codomain() + for phi in phis: + if phi.domain() != domain: + raise ValueError(f'summand {phi} has incorrect domain (need {domain})') + if phi.codomain() != codomain: + raise ValueError(f'summand {phi} has incorrect codomain (need {codomain})') + + self._phis = phis + self._domain = domain + self._codomain = codomain + + # We temporarily overwrite the _degree attribute here to prevent the + # EllipticCurveHom constructor from attempting to compute the degree. + self._degree = 0 + EllipticCurveHom.__init__(self, self._domain, self._codomain) + self._degree = None + + def _call_(self, P): + r""" + Evaluate this sum morphism at a point. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: P = E.lift_x(0) + sage: (phi + phi)(P) + (72 : 56 : 1) + sage: (phi - phi)(P) + (0 : 1 : 0) + """ + return sum((phi(P) for phi in self._phis), self._codomain(0)) + + def _eval(self, P): + r""" + Less strict evaluation method for internal use. + + In particular, this can be used to evaluate ``self`` at a + point defined over an extension field. + + INPUT: a sequence of 3 coordinates defining a point on ``self`` + + OUTPUT: the result of evaluating ``self`` at the given point + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: P = E.change_ring(GF(101^2)).lift_x(1) + sage: (phi + phi)._eval(P) + (11 : 15*z2 + 71 : 1) + sage: (phi - phi)._eval(P) + (0 : 1 : 0) + """ + if self._domain.defining_polynomial()(*P): + raise ValueError(f'{P} not on {self._domain}') + k = Sequence(P).universe() + return sum((phi._eval(P) for phi in self._phis), self._codomain.base_extend(k)(0)) + + def _repr_(self): + r""" + Return basic facts about this sum morphism as a string. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: phi + phi # indirect doctest + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101) + """ + return f'Sum morphism:' \ + f'\n From: {self._domain}' \ + f'\n To: {self._codomain}' \ + f'\n Via: {self._phis}' + + def summands(self): + r""" + Return the individual summands making up this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(j=5) + sage: m2 = E.scalar_multiplication(2) + sage: m3 = E.scalar_multiplication(3) + sage: m2 + m3 + Sum morphism: + From: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field + To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field + Via: (Scalar-multiplication endomorphism [2] of Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field, Scalar-multiplication endomorphism [3] of Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field) + """ + return self._phis + + @cached_method + def to_isogeny_chain(self): + r""" + Convert this formal sum of elliptic-curve morphisms into a + :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite` + object representing the same morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).to_isogeny_chain() + Composite morphism of degree 28 = 4*1*7: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + + :: + + sage: p = 419 + sage: E = EllipticCurve(GF(p^2), [1,0]) + sage: iota = E.automorphisms()[2] # sqrt(-1) + sage: pi = E.frobenius_isogeny() # sqrt(-p) + sage: endo = iota + pi + sage: endo.degree() + 420 + sage: endo.to_isogeny_chain() + Composite morphism of degree 420 = 4*1*3*5*7: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 419^2 + To: Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 419^2 + + The decomposition is impossible for the constant zero map:: + + sage: endo = iota*pi + pi*iota + sage: endo.degree() + 0 + sage: endo.to_isogeny_chain() + Traceback (most recent call last): + ... + ValueError: zero morphism cannot be written as a composition of isogenies + + Isomorphisms are supported as well:: + + sage: E = EllipticCurve(j=5); E + Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field + sage: m2 = E.scalar_multiplication(2) + sage: m3 = E.scalar_multiplication(3) + sage: (m2 - m3).to_isogeny_chain() + Composite morphism of degree 1 = 1^2: + From: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field + To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 180*x + 17255 over Rational Field + sage: (m2 - m3).rational_maps() + (x, -x - y) + """ + deg = self.degree() + if deg.is_zero(): + raise ValueError('zero morphism cannot be written as a composition of isogenies') + + p = self.base_ring().characteristic() + insep = self.inseparable_degree().valuation(p) if p else 0 + + scalar = 1 #TODO Can we detect scalar factors earlier to save some extensions below? + + ker = [] + for l,m in deg.factor(): + if l == p: # possibly inseparable + if insep < m: + # kernel of the separable p-power part is unique + P = point_of_order(self.domain(), p**(m-insep)) + ker.append(P) + continue + +# F = self.domain().division_field(l**m) #FIXME this can be used once #35936 is done; workaround below + F = self.domain().division_polynomial(l**m).splitting_field('X').extension(2,'Y') + + P,Q = self.domain().change_ring(F).torsion_basis(l**m) + if self.is_endomorphism(): + R,S = P,Q + else: + R,S = self.codomain().change_ring(F).torsion_basis(l**m) + M = self.matrix_on_subgroup((P,Q), (R,S)) + g = ZZ(gcd(M.list())).p_primary_part(l) + if g > 1: + scalar *= g + M = (M.change_ring(ZZ) / g).change_ring(M.base_ring()) + K = M.left_kernel_matrix() + for row in K: + u,v = map(ZZ, row) + pt = u*P + v*Q + pt.set_order(row.additive_order()) + ker.append(pt) + + from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + phi = EllipticCurveHom_composite(self.domain(), []) + + if scalar != 1: + phi *= phi.codomain().scalar_multiplication(scalar) + + while ker: + K = ker.pop(0) + + (l,e), = K.order().factor() + for i in reversed(range(e)): + Kl = l**i * K + Kl.set_order(l) + + from sage.groups.generic import multiples + from sage.misc.misc_c import prod + x = polygen(Kl.base_ring()) + poly = prod(x - T.xy()[0] for T in multiples(Kl, l//2, Kl)) + poly = poly.change_ring(self.base_ring()) + + psi = phi.codomain().isogeny(poly) + phi = psi * phi + K = psi._eval(K) + ker = [psi._eval(P) for P in ker] + + if insep: + frob = phi.codomain().frobenius_isogeny(insep) + phi = frob * phi + + from sage.schemes.elliptic_curves.hom import find_post_isomorphism + iso = find_post_isomorphism(phi, self) + return iso * phi + + # EllipticCurveHom methods + + def _degree_bounds(self): + r""" + Return a lower and upper bound on the degree of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(307), [5,5]) + sage: phi = E.isogenies_prime_degree(3)[0]; phi + Isogeny of degree 3 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 307 to Elliptic Curve defined by y^2 = x^3 + 227*x + 163 over Finite Field of size 307 + sage: psi = next(iso*psi for psi in E.isogenies_prime_degree(43) + ....: for iso in psi.codomain().isomorphisms(phi.codomain())); psi + Isogeny of degree 43 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 307 to Elliptic Curve defined by y^2 = x^3 + 227*x + 163 over Finite Field of size 307 + sage: (phi + psi)._degree_bounds() + (24, 68) + sage: (phi + psi).degree() + 61 + sage: (phi - phi)._degree_bounds() + (0, 12) + sage: (phi - phi).degree() + 0 + + :: + + sage: E = EllipticCurve(GF(443), [1,1]) + sage: pi = E.frobenius_endomorphism() + sage: m1 = E.scalar_multiplication(1) + sage: (pi - m1)._degree_bounds() + (402, 486) + sage: (pi - m1)._degree_bounds() == Hasse_bounds(443) + True + sage: (pi - m1).degree() + 433 + + ALGORITHM: Repeated application of the Cauchy-Schwarz inequality, + here in the form + `|\deg(f+g) - \deg(f) - \deg(g)| \leq 2\sqrt{\deg(f)\cdot\deg(g)}`. + See for instance Lemma V.1.2 of [Sil2009]_. + """ + lo, hi = ZZ.zero(), ZZ.zero() + for phi in self._phis: + m = (hi * phi.degree()).isqrt() + hi += phi.degree() + 2*m + lo += phi.degree() - 2*m + lo = max(lo, 0) + return lo, hi + + def _compute_degree(self): + r""" + Internal method to compute and cache the degree of this sum morphism + (and its dual). + + ALGORITHM: Evaluate the composition with the dual on points of small + order and solve logarithms to eventually recover the degree using CRT. + (This is essentially Schoof's algorithm, applied to a scalar.) + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: isog = phi + phi + sage: print(isog._degree) + None + sage: isog._compute_degree() + sage: isog._degree + 28 + + :: + + sage: E = EllipticCurve(GF(443), [1,1]) + sage: pi = E.frobenius_endomorphism() + sage: m1 = E.scalar_multiplication(1) + sage: endo = pi - m1 + sage: print(endo._degree) + None + sage: endo._compute_degree() + sage: endo._degree + 433 + sage: endo.dual()._degree + 433 + """ + if self._degree is not None: + return + if len(self._phis) == 0: + self._degree = 0 + elif len(self._phis) == 1: + self._degree = self._phis[0].degree() + else: + #TODO In some cases it would probably be faster to simply + # compute the kernel polynomial using the addition formulas? + from sage.rings.finite_rings.integer_mod import Mod + + lo, hi = self._degree_bounds() + M = hi - lo + 1 + rem = Mod(0,1) + for l in Primes(): + if rem.modulus() >= M: + break + try: + P = point_of_order(self._domain, l) + except ValueError: + continue # supersingular and l == p + + Q = self.dual()._eval(self._eval(P)) + d = discrete_log(Q, P, ord=l, operation='+') + rem = rem.crt(Mod(d-lo, l)) + + self._degree = lo + rem.lift() + self.dual()._degree = self._degree + + @staticmethod + def _comparison_impl(left, right, op): + r""" + Compare a sum morphism to another elliptic-curve morphism. + + Called by :meth:`EllipticCurveHom._richcmp_`. + + If possible, we use + :func:`~sage.schemes.elliptic_curves.hom.compare_via_evaluation`. + The complexity in that case is polynomial in the logarithm of + the degree. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom_sum import EllipticCurveHom_sum + sage: E = EllipticCurve(GF(419^2), [1,0]) + sage: i = E.automorphisms()[-1] + sage: j = E.frobenius_isogeny() + sage: i + j == j + i + True + """ + from sage.structure.richcmp import op_EQ + if op != op_EQ: + return NotImplemented + try: + return compare_via_evaluation(left, right) + except NotImplementedError: + return NotImplemented + + def degree(self): + r""" + Return the degree of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).degree() + 28 + + This method yields a simple toy point-counting algorithm:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: m1 = E.scalar_multiplication(1) + sage: pi = E.frobenius_endomorphism() + sage: (pi - m1).degree() + 119 + sage: E.count_points() + 119 + + ALGORITHM: Essentially Schoof's algorithm; see :meth:`_compute_degree`. + """ + if self._degree is None: + self._compute_degree() + return self._degree + + def rational_maps(self): + r""" + Return the rational maps of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).rational_maps() + ((5*x^28 + 43*x^27 + 26*x^26 - ... + 7*x^2 - 23*x + 38)/(23*x^27 + 16*x^26 + 9*x^25 + ... - 43*x^2 - 22*x + 37), + (42*x^42*y - 44*x^41*y - 22*x^40*y + ... - 26*x^2*y - 50*x*y - 18*y)/(-24*x^42 - 47*x^41 - 12*x^40 + ... + 18*x^2 - 48*x + 18)) + + ALGORITHM: :meth:`to_isogeny_chain`. + """ + #TODO In some cases it would probably be faster to compute this + # directly using the addition formulas? + return self.to_isogeny_chain().rational_maps() + + def x_rational_map(self): + r""" + Return the `x`-coordinate rational map of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).x_rational_map() + (9*x^28 + 37*x^27 + 67*x^26 + ... + 53*x^2 + 100*x + 28)/(x^27 + 49*x^26 + 97*x^25 + ... + 64*x^2 + 21*x + 6) + + ALGORITHM: :meth:`to_isogeny_chain`. + """ + #TODO In some cases it would probably be faster to compute this + # directly using the addition formulas? + return self.to_isogeny_chain().x_rational_map() + + def kernel_polynomial(self): + r""" + Return the kernel polynomial of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).kernel_polynomial() + x^15 + 75*x^14 + 16*x^13 + 59*x^12 + 28*x^11 + 60*x^10 + 69*x^9 + 79*x^8 + 79*x^7 + 52*x^6 + 35*x^5 + 11*x^4 + 37*x^3 + 69*x^2 + 66*x + 63 + + :: + + sage: E = EllipticCurve(GF(11), [5,5]) + sage: pi = E.frobenius_endomorphism() + sage: m1 = E.scalar_multiplication(1) + sage: (pi - m1).kernel_polynomial() + x^9 + 7*x^8 + 2*x^7 + 4*x^6 + 10*x^4 + 4*x^3 + 9*x^2 + 7*x + + ALGORITHM: :meth:`to_isogeny_chain`. + """ + #TODO In some cases it would probably be faster to compute this + # directly using the addition formulas? + return self.to_isogeny_chain().kernel_polynomial() + + def scaling_factor(self): + r""" + Return the Weierstrass scaling factor associated to this + sum morphism. + + The scaling factor is the constant `u` (in the base field) + such that `\varphi^* \omega_2 = u \omega_1`, where + `\varphi: E_1\to E_2` is this morphism and `\omega_i` are + the standard Weierstrass differentials on `E_i` defined by + `\mathrm dx/(2y+a_1x+a_3)`. + + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: phi.scaling_factor() + 84 + sage: (phi + phi).scaling_factor() + 67 + + ALGORITHM: The scaling factor is additive under addition + of elliptic-curve morphisms, so we simply add together the + scaling factors of the :meth:`summands`. + """ + return sum(phi.scaling_factor() for phi in self._phis) + + @cached_method + def dual(self): + r""" + Return the dual of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [5,5]) + sage: phi = E.isogenies_prime_degree(7)[0] + sage: (phi + phi).dual() + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101 + Via: (Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101, Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 12*x + 98 over Finite Field of size 101 to Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 101) + sage: (phi + phi).dual() == phi.dual() + phi.dual() + True + + :: + + sage: E = EllipticCurve(GF(431^2), [1,0]) + sage: iota = E.automorphisms()[2] + sage: m2 = E.scalar_multiplication(2) + sage: endo = m2 + iota + sage: endo.dual() + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 431^2 + To: Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 431^2 + Via: (Scalar-multiplication endomorphism [2] of Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 431^2, Elliptic-curve endomorphism of Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 431^2 + Via: (u,r,s,t) = (8*z2 + 427, 0, 0, 0)) + sage: endo.dual() == (m2 - iota) + True + + ALGORITHM: Taking the dual distributes over addition. + """ + psi = EllipticCurveHom_sum((phi.dual() for phi in self._phis), + domain=self._codomain, codomain=self._domain) + psi._degree = self._degree + if self.trace.is_in_cache(): + psi.trace.set_cache(-self.trace.cache) + psi.dual.set_cache(self) + return psi + + @cached_method + def inseparable_degree(self): + r""" + Compute the inseparable degree of this sum morphism. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(7), [0,1]) + sage: m3 = E.scalar_multiplication(3) + sage: m3.inseparable_degree() + 1 + sage: m4 = E.scalar_multiplication(4) + sage: m7 = m3 + m4; m7 + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 + To: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 + Via: (Scalar-multiplication endomorphism [3] of Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7, Scalar-multiplication endomorphism [4] of Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7) + sage: m7.degree() + 49 + sage: m7.inseparable_degree() + 7 + + A supersingular example:: + + sage: E = EllipticCurve(GF(7), [1,0]) + sage: m3 = E.scalar_multiplication(3) + sage: m3.inseparable_degree() + 1 + sage: m4 = E.scalar_multiplication(4) + sage: m7 = m3 + m4; m7 + Sum morphism: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 7 + To: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 7 + Via: (Scalar-multiplication endomorphism [3] of Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 7, Scalar-multiplication endomorphism [4] of Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 7) + sage: m7.inseparable_degree() + 49 + """ + if self.is_zero(): + raise ValueError('zero morphism is not an isogeny') + + p = self.base_ring().characteristic() + if not p: + return ZZ.one() + + m = self.degree().valuation(p) + if not m: + return ZZ.one() + + try: + P = point_of_order(self.domain(), p**m) + except ValueError: + # supersingular; every p-isogeny is purely inseparable + return p**m + + Q = self._eval(P) + return order_from_multiple(Q, p**m) diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 4bc84d944f8..fbe4887dbae 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -1173,21 +1173,21 @@ def scaling_factor(self): """ return self._pre_iso.scaling_factor() * self._post_iso.scaling_factor() - def is_separable(self): + def inseparable_degree(self): r""" - Determine whether or not this isogeny is separable. + Return the inseparable degree of this square-root VĂ©lu + isogeny. Since :class:`EllipticCurveHom_velusqrt` only implements - separable isogenies, this method always returns ``True``. + separable isogenies, this method always returns one. - EXAMPLES:: + TESTS:: - sage: E = EllipticCurve(GF(17), [0,0,0,3,0]) - sage: phi = E.isogeny(E((1,2)), algorithm='velusqrt') - sage: phi.is_separable() - True + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: EllipticCurveHom_velusqrt.inseparable_degree(None) + 1 """ - return True + return Integer(1) def _random_example_for_testing(): diff --git a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py index 57ff015a2b2..d6027e2d195 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -849,21 +849,6 @@ def kernel_polynomial(self): """ return self._poly_ring(1) - def is_separable(self): - r""" - Determine whether or not this isogeny is separable. - - Since :class:`WeierstrassIsomorphism` only implements - isomorphisms, this method always returns ``True``. - - EXAMPLES:: - - sage: E = EllipticCurve(GF(31337), [0,1]) # needs sage.rings.finite_rings - sage: {f.is_separable() for f in E.automorphisms()} # needs sage.rings.finite_rings - {True} - """ - return True - def dual(self): """ Return the dual isogeny of this isomorphism. @@ -956,6 +941,20 @@ def scaling_factor(self): """ return self.u + def inseparable_degree(self): + r""" + Return the inseparable degree of this Weierstrass isomorphism. + + For isomorphisms, this method always returns one. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism + sage: WeierstrassIsomorphism.inseparable_degree(None) + 1 + """ + return Integer(1) + def identity_morphism(E): r"""