diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 57003863b1b..3fd4df3961a 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -53,6 +53,7 @@ # **************************************************************************** import math +from sage.arith.misc import valuation import sage.rings.abc from sage.rings.finite_rings.integer_mod import mod @@ -65,6 +66,7 @@ import sage.groups.generic as generic from sage.arith.functions import lcm +from sage.functions.generalized import sgn from sage.rings.integer import Integer from sage.rings.big_oh import O from sage.rings.infinity import Infinity as oo @@ -2019,6 +2021,30 @@ def division_polynomial(self, m, x=None, two_torsion_multiplicity=2, force_evalu torsion_polynomial = division_polynomial + @cached_method + def multiplication_by_p_isogeny(self): + r""" + Return the multiplication-by-\(p\) isogeny. + + EXAMPLES:: + + sage: p = 23 + sage: K. = GF(p^3) + sage: E = EllipticCurve(K, [K.random_element(), K.random_element()]) + sage: phi = E.multiplication_by_p_isogeny() + sage: assert phi.degree() == p**2 + sage: P = E.random_element() + sage: assert phi(P) == P * p + """ + from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic + + K = self.base_ring() + if not isinstance(K, FiniteField_generic): + raise ValueError(f"Base ring (={K}) is not a finite field.") + + frob = self.frobenius_isogeny() + return frob.dual() * frob + def _multiple_x_numerator(self, n, x=None): r""" Return the numerator of the `x`-coordinate of the `n\th` multiple of a @@ -2333,6 +2359,20 @@ def multiplication_by_m(self, m, x_only=False): sage: my_eval = lambda f,P: [fi(P[0],P[1]) for fi in f] sage: f = E.multiplication_by_m(2) sage: assert(E(eval(f,P)) == 2*P) + + The following test shows that :trac:`6413` is indeed fixed:: + sage: p = 7 + sage: K. = GF(p^2) + sage: E = EllipticCurve(K, [a + 3, 5 - a]) + sage: k = p^2 * 3 + sage: f, g = E.multiplication_by_m(k) + sage: for _ in range(100): + ....: P = E.random_point() + ....: if P * k == 0: + ....: continue + ....: Qx = f.subs(x=P[0]) + ....: Qy = g.subs(x=P[0], y=P[1]) + ....: assert (P * k).xy() == (Qx, Qy) """ # Coerce the input m to be an integer m = Integer(m) @@ -2352,7 +2392,7 @@ def multiplication_by_m(self, m, x_only=False): return x # Grab curve invariants - a1, a2, a3, a4, a6 = self.a_invariants() + a1, _, a3, _, _ = self.a_invariants() if m == -1: if not x_only: @@ -2360,22 +2400,43 @@ def multiplication_by_m(self, m, x_only=False): else: return x - # the x-coordinate does not depend on the sign of m. The work + # Inseparable cases + # Special case of multiplication by p is easy. Kind of. + p = Integer(self.base_ring().characteristic()) + + v_p = 0 if p == 0 else valuation(m.abs(), p) + m //= p**v_p + + # the x-coordinate does not depend on the sign of m. The work # here is done by functions defined earlier: mx = (x.parent()(self._multiple_x_numerator(m.abs(), x)) / x.parent()(self._multiple_x_denominator(m.abs(), x))) + if x_only: + # slow. + if v_p > 0: + p_endo = self.multiplication_by_p_isogeny() + isog = p_endo**v_p + fx = isog.x_rational_map() + # slow. + mx = mx.subs(x=fx) # Return it if the optional parameter x_only is set. return mx - # Consideration of the invariant differential - # w=dx/(2*y+a1*x+a3) shows that m*w = d(mx)/(2*my+a1*mx+a3) - # and hence 2*my+a1*mx+a3 = (1/m)*(2*y+a1*x+a3)*d(mx)/dx - + # Consideration of the invariant differential + # w=dx/(2*y+a1*x+a3) shows that m*w = d(mx)/(2*my+a1*mx+a3) + # and hence 2*my+a1*mx+a3 = (1/m)*(2*y+a1*x+a3)*d(mx)/dx my = ((2*y+a1*x+a3)*mx.derivative(x)/m - a1*mx-a3)/2 + if v_p > 0: + frob = self.frobenius_isogeny() + isog = (frob.dual() * frob)**v_p + fx, fy = isog.rational_maps() + # slow... + my = my.subs(x=fx, y=fy) + return mx, my def multiplication_by_m_isogeny(self, m):