Repository: sagemath/sage

Issue:
Retrieving the multiplication-by-p isogeny on elliptic curves over fields of characteristic p fails.
<div id="comment:0"></div>


```
sage: p = 11
sage: E = EllipticCurve(GF(p), [1,1])
sage: E.multiplication_by_m(p)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)

/Users/hlaw/.sage/temp/resid_tg082.upc.es/80890/_Users_hlaw__sage_init_sage_0.py in <module>()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/schemes/elliptic_curves/ell_generic.pyc in multiplication_by_m(self, m, x_only)
   2133         #  and hence 2*my+a1*mx+a3 = (1/m)*(2*y+a1*x+a3)*d(mx)/dx
   2134 
-> 2135         my = ((2*y+a1*x+a3)*mx.derivative(x)/m - a1*mx-a3)/2
   2136 
   2137         return mx, my

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/element.so in sage.structure.element.RingElement.__div__ (sage/structure/element.c:10361)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/coerce.so in sage.structure.coerce.CoercionModel_cache_maps.bin_op (sage/structure/coerce.c:6105)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/element.so in sage.structure.element.RingElement.__div__ (sage/structure/element.c:10342)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/rings/fraction_field_element.so in sage.rings.fraction_field_element.FractionFieldElement._div_ (sage/rings/fraction_field_element.c:5805)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/rings/fraction_field_element.so in sage.rings.fraction_field_element.FractionFieldElement.__init__ (sage/rings/fraction_field_element.c:1954)()

ZeroDivisionError: fraction field element division by zero
```
One can see from the line that causes the error that the calculation of the *y*-component of the isogeny involves a division by *m*, which in this case is the characteristic of the ground field.


CC:  @defeo @jpflori @yyyyx4

Component: **elliptic curves**

_Issue created by migration from https://trac.sagemath.org/ticket/6413_


Retrieving the multiplication-by-p isogeny on elliptic curves over fields of characteristic p fails.
<div id="comment:0"></div>


```
sage: p = 11
sage: E = EllipticCurve(GF(p), [1,1])
sage: E.multiplication_by_m(p)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)

/Users/hlaw/.sage/temp/resid_tg082.upc.es/80890/_Users_hlaw__sage_init_sage_0.py in <module>()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/schemes/elliptic_curves/ell_generic.pyc in multiplication_by_m(self, m, x_only)
   2133         #  and hence 2*my+a1*mx+a3 = (1/m)*(2*y+a1*x+a3)*d(mx)/dx
   2134 
-> 2135         my = ((2*y+a1*x+a3)*mx.derivative(x)/m - a1*mx-a3)/2
   2136 
   2137         return mx, my

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/element.so in sage.structure.element.RingElement.__div__ (sage/structure/element.c:10361)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/coerce.so in sage.structure.coerce.CoercionModel_cache_maps.bin_op (sage/structure/coerce.c:6105)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/structure/element.so in sage.structure.element.RingElement.__div__ (sage/structure/element.c:10342)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/rings/fraction_field_element.so in sage.rings.fraction_field_element.FractionFieldElement._div_ (sage/rings/fraction_field_element.c:5805)()

/Users/hlaw/src/sage-4.0.1/local/lib/python2.5/site-packages/sage/rings/fraction_field_element.so in sage.rings.fraction_field_element.FractionFieldElement.__init__ (sage/rings/fraction_field_element.c:1954)()

ZeroDivisionError: fraction field element division by zero
```
One can see from the line that causes the error that the calculation of the *y*-component of the isogeny involves a division by *m*, which in this case is the characteristic of the ground field.


CC:  @defeo @jpflori @yyyyx4

Component: **elliptic curves**

_Issue created by migration from https://trac.sagemath.org/ticket/6413_




Discussion:
<div id="comment:1" align="right">comment:1</div>

I wrote the offending line of code, knowing full well that it would not work in the inseparable case.  I may or may not have made a ticket at the time, but likely not.

When someone writes code to deal with the inseparable case, just make sure that it continues to be efficient in the separable case!
<div id="comment:1" align="right">comment:1</div>

I wrote the offending line of code, knowing full well that it would not work in the inseparable case.  I may or may not have made a ticket at the time, but likely not.

When someone writes code to deal with the inseparable case, just make sure that it continues to be efficient in the separable case!

Resolving patch:
diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py
--- a/src/sage/schemes/elliptic_curves/ell_finite_field.py
+++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py
@@ -610,6 +610,24 @@ def cardinality(self, algorithm=None, extension_degree=1):
 
     order = cardinality  # alias
 
+    @cached_method
+    def multiplication_by_p_isogeny(self):
+        r"""
+        Return the multiplication-by-`p` isogeny.
+
+        EXAMPLES::
+
+            sage: p = 23
+            sage: K.<a> = GF(p^3)
+            sage: E = EllipticCurve(j=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
+        """
+        frob = self.frobenius_isogeny()
+        return frob.dual() * frob
+
     def frobenius_polynomial(self):
         r"""
         Return the characteristic polynomial of Frobenius.
@@ -1773,13 +1791,7 @@ def twists(self):
             sage: p = next_prime(randrange(2,100))
             sage: e = randrange(1,10)
             sage: F.<t> = GF((p,e))
-            sage: while True:
-            ....:     try:
-            ....:         E = EllipticCurve([F.random_element() for _ in range(5)])
-            ....:     except ArithmeticError:
-            ....:         pass
-            ....:     else:
-            ....:         break
+            sage: E = EllipticCurve(j=F.random_element())
             sage: twists1 = E.twists()
             sage: {sum(E1.is_isomorphic(E2) for E2 in twists1) == 1 for E1 in twists1}
             {True}
diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py
--- 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
@@ -66,8 +67,6 @@
 
 from sage.arith.functions import lcm
 from sage.rings.integer import Integer
-from sage.rings.big_oh import O
-from sage.rings.infinity import Infinity as oo
 from sage.rings.rational import Rational
 from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF
 from sage.rings.rational_field import RationalField
@@ -2421,15 +2420,53 @@ 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 fixed for elliptic curves over finite fields::
+            sage: p = 7
+            sage: K.<a> = 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)
+
+        However, it still fails for elliptic curves over positive-characteristic fields::
+
+            sage: F.<a> = FunctionField(GF(7))
+            sage: E = EllipticCurve(F, [a, 1 / a])
+            sage: E.multiplication_by_m(7)
+            Traceback (most recent call last):
+            ...
+            NotImplementedError: multiplication by integer not coprime to p is only implemented for curves over finite fields
+
+        ::
+
+            sage: p = 7
+            sage: K.<a> = GF(p^2)
+            sage: E = EllipticCurve(K, [K.random_element(), K.random_element()])
+            sage: E.multiplication_by_m(p * 2)[0] == E.multiplication_by_m(p * 2, x_only=True)
+            True
         """
         # Coerce the input m to be an integer
         m = Integer(m)
+        p = self.base_ring().characteristic()
+
         if m == 0:
             raise ValueError("m must be a non-zero integer")
 
         if x_only:
             x = polygen(self.base_ring(), 'x')
         else:
+            from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic
+            if p != 0 and m % p == 0 and not isinstance(self.base_ring(), FiniteField_generic):
+                # TODO: Implement the correct formula?
+                raise NotImplementedError("multiplication by integer not coprime to p "
+                                          "is only implemented for curves over finite fields")
             x, y = polygens(self.base_ring(), 'x,y')
 
         # Special case of multiplication by 1 is easy.
@@ -2440,7 +2477,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:
@@ -2448,22 +2485,33 @@ 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
+        # If we only require the x coordinate, it is faster to use the recursive formula
+        # since substituting polynomials is quite slow.
+        v_p = 0 if p == 0 else valuation(m, p)
+        if not x_only:
+            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:
-            # 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:
+            isog = self.multiplication_by_p_isogeny()**v_p
+            fx, fy = isog.rational_maps()
+            # slow...
+            mx = mx.subs(x=fx)
+            my = my.subs(x=fx, y=fy)
+
         return mx, my
 
     def multiplication_by_m_isogeny(self, m):


Test patch: