Repository: sagemath/sage

Issue:
Elliptic curves with CM over number fields fail to find all isogenies
### Steps To Reproduce

Define an elliptic curve E over a real quadratic number field, where j(E) = 0, compute E.isogeny_class(), and look at the degrees it finds. Example:
sage: L5.<r5> = NumberField(x^2-5)
sage: F = EllipticCurve(L5,[0,-4325477943600\*r5-4195572876000])
sage: F.isogeny_class().matrix()
[1 3]
[3 1]

### Expected Behavior

The matrix should include isogenies of degree 5 (and more).

### Actual Behavior

It only find 3-isogenies (see above).


### Additional Information

As reported to sage-support: https://groups.google.com/g/sage-support/c/1BfJIGIlb0M

The problem is in the function sage.schemes.elliptic_curves.isogeny_class.isogeny_degrees.cm, where the degree bound called n there needs to be multiplied by 3 for j=0, d=-3 (and by 2 for j=1728, d=-4).

A PR is in preparation.  Only curves with j=0 or j=1728 are affected (and not all these).

### Environment

```markdown
- **OS**: any
- **Sage Version**: 10.2rc3
```


### Checklist

- [X] I have searched the existing issues for a bug report that matches the one I want to file, without success.
- [X] I have read the documentation and troubleshoot guide


Discussion:

Resolving patch:
diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py
--- a/src/sage/schemes/elliptic_curves/ell_number_field.py
+++ b/src/sage/schemes/elliptic_curves/ell_number_field.py
@@ -2919,6 +2919,18 @@ class number is only `3` is that the class also contains three
             sage: E = EllipticCurve([a+1, 1, 1, 0, 0])
             sage: C = E.isogeny_class(); len(C) # long time
             4
+
+        Check that :issue:`36780` is fixed::
+
+            sage: L5.<r5> = NumberField(x^2-5)
+            sage: F = EllipticCurve(L5,[0,-4325477943600 *r5-4195572876000])
+            sage: F.isogeny_class().matrix()
+            [ 1 25 75  3  5 15]
+            [25  1  3 75  5 15]
+            [75  3  1 25 15  5]
+            [ 3 75 25  1 15  5]
+            [ 5  5 15 15  1  3]
+            [15 15  5  5  3  1]
         """
         try:
             return self._isoclass
@@ -3283,10 +3295,10 @@ def reducible_primes(self, algorithm='Billerey', max_l=None,
         For curves without CM the list returned is exactly the finite
         set of primes `\ell` for which the mod-`\ell` Galois
         representation is reducible.  For curves with CM this set is
-        infinite; we return a finite list of primes `\ell` such that
-        every curve isogenous to this curve can be obtained by a
-        finite sequence of isogenies of degree one of the primes in
-        the list.
+        infinite; we return a (not necessarily minimal) finite list
+        of primes `\ell` such that every curve isogenous to this curve
+        can be obtained by a finite sequence of isogenies of degree one
+        of the primes in the list.
 
         INPUT:
 
@@ -3328,7 +3340,7 @@ def reducible_primes(self, algorithm='Billerey', max_l=None,
             sage: rho.reducible_primes() # CM curves always return [0]
             [0]
             sage: E.reducible_primes()
-            [2]
+            [2, 5]
             sage: E = EllipticCurve_from_j(K(0)) # CM but NOT over K
             sage: rho = E.galois_representation()
             sage: rho.reducible_primes() # long time
diff --git a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py
--- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py
+++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py
@@ -740,10 +740,11 @@ def _exceptionals(E, L, patience=1000):
 
 def _over_numberfield(E):
     r"""
-    Return `E`, defined over a ``NumberField`` object.
+    Return `E`, defined over an absolute ``NumberField``.
 
     This is necessary since if `E` is defined over `\QQ`, then we
-    cannot use Sage commands available for number fields.
+    cannot use Sage commands available for number fields, and if the
+    base field is relative then other problems result.
 
     INPUT:
 
@@ -751,26 +752,27 @@ def _over_numberfield(E):
 
     OUTPUT:
 
-    - If `E` is defined over a NumberField, returns E.
+    - If `E` is defined over an absolute number field, returns `E`.
 
-    - If `E` is defined over QQ, returns E defined over the NumberField QQ.
+    - If `E` is defined over a relative number field, returns `E` defined over the absolute base field.
+
+    - If `E` is defined over `\QQ`, returns `E` defined over the number field `\QQ`.
 
     EXAMPLES::
 
         sage: E = EllipticCurve([1, 2])
         sage: sage.schemes.elliptic_curves.gal_reps_number_field._over_numberfield(E)
         Elliptic Curve defined by y^2 = x^3 + x + 2 over Number Field in a with defining polynomial x
-    """
 
+    """
     K = E.base_field()
 
     if K == QQ:
-        x = QQ['x'].gen()
-        K = NumberField(x, 'a')
-        E = E.change_ring(K)
-
-    return E
-
+        from sage.rings.polynomial.polynomial_ring import polygen
+        K = NumberField(polygen(QQ), 'a')
+    else:
+        K = K.absolute_field('a')
+    return E.change_ring(K)
 
 def deg_one_primes_iter(K, principal_only=False):
     r"""
diff --git a/src/sage/schemes/elliptic_curves/isogeny_class.py b/src/sage/schemes/elliptic_curves/isogeny_class.py
--- a/src/sage/schemes/elliptic_curves/isogeny_class.py
+++ b/src/sage/schemes/elliptic_curves/isogeny_class.py
@@ -1135,7 +1135,8 @@ def isogeny_degrees_cm(E, verbose=False):
 
     A finite list of primes `\ell` such that every curve isogenous to
     this curve can be obtained by a finite sequence of isogenies of
-    degree one of the primes in the list.
+    degree one of the primes in the list.  This list is not
+    necessarily minimal.
 
     ALGORITHM:
 
@@ -1188,8 +1189,20 @@ def isogeny_degrees_cm(E, verbose=False):
         downward split primes: {2, 3}
         downward inert primes: {5}
         primes generating the class group: [2]
-        Complete set of primes: {2, 3, 5}
-        [2, 3, 5]
+        Set of primes before filtering: {2, 3, 5}
+        List of primes after filtering: [2, 3]
+        [2, 3]
+
+    TESTS:
+
+    Check that :issue:`36780` is fixed::
+
+        sage: L5.<r5> = NumberField(x^2-5)
+        sage: E = EllipticCurve(L5,[0,-4325477943600 *r5-4195572876000])
+        sage: from sage.schemes.elliptic_curves.isogeny_class import isogeny_degrees_cm
+        sage: isogeny_degrees_cm(E)
+        [3, 5]
+
     """
     if not E.has_cm():
         raise ValueError("possible_isogeny_degrees_cm(E) requires E to be an elliptic curve with CM")
@@ -1205,6 +1218,11 @@ def isogeny_degrees_cm(E, verbose=False):
     n = E.base_field().absolute_degree()
     if not E.has_rational_cm():
         n *= 2
+    # For discriminants with extra units there's an extra factor in the class number formula:
+    if d == -4:
+        n *= 2
+    if d == -3:
+        n *= 3
     divs = n.divisors()
 
     data = pari(d).quadclassunit()
@@ -1232,7 +1250,7 @@ def isogeny_degrees_cm(E, verbose=False):
     # of the order O of discriminant d.  The latter case can only
     # happen when l^2 divides d.
 
-    # Compute the ramified primes
+    # (a) ramified primes
 
     ram_l = d.odd_part().prime_factors()
 
@@ -1247,25 +1265,22 @@ def isogeny_degrees_cm(E, verbose=False):
 
     else:
 
-        # Find the "upward" primes (index divided by l):
+        # "Upward" primes (index divided by l):
 
         L1 = Set([l for l in ram_l if d.valuation(l) > 1])
         L += L1
         if verbose:
             print("upward primes: %s" % L1)
 
-        # Find the "downward" primes (index multiplied by l, class
-        # number multiplied by l-kronecker_symbol(d,l)):
-
-        # (a) ramified primes; the suborder has class number l*h, so l
-        # must divide n/2h:
+        # "Downward" ramified primes; index multiplied by l, class
+        # number multiplied by l, so l must divide n/2h:
 
         L1 = Set([l for l in ram_l if l.divides(n_over_2h)])
         L += L1
         if verbose:
             print("downward ramified primes: %s" % L1)
 
-    # (b) split primes; the suborder has class number (l-1)*h, so
+    # (b) Downward split primes; the suborder has class number (l-1)*h, so
     # l-1 must divide n/2h:
 
     L1 = Set([lm1+1 for lm1 in divs
@@ -1274,7 +1289,7 @@ def isogeny_degrees_cm(E, verbose=False):
     if verbose:
         print("downward split primes: %s" % L1)
 
-    # (c) inert primes; the suborder has class number (l+1)*h, so
+    # (c) Downward inert primes; the suborder has class number (l+1)*h, so
     # l+1 must divide n/2h:
 
     L1 = Set([lp1-1 for lp1 in divs
@@ -1283,10 +1298,9 @@ def isogeny_degrees_cm(E, verbose=False):
     if verbose:
         print("downward inert primes: %s" % L1)
 
-    # Now find primes represented by each form of discriminant d.
-    # In the rational CM case, we use all forms associated to
-    # generators of the class group, otherwise only forms of order
-    # 2:
+    # Horizontal primes (rational CM only): same order, degrees are
+    # all integers represented by some binary quadratic form of
+    # discriminant d, so we find a prime represented by each form.
 
     if E.has_rational_cm():
         from sage.quadratic_forms.binary_qf import BinaryQF
@@ -1300,10 +1314,14 @@ def isogeny_degrees_cm(E, verbose=False):
     # Return sorted list
 
     if verbose:
-        print("Complete set of primes: %s" % L)
-
-    return sorted(L)
+        print("Set of primes before filtering: %s" % L)
 
+    # This filter will quickly eliminate most false entries in the set
+    from .gal_reps_number_field import Frobenius_filter
+    L = Frobenius_filter(E, sorted(L))
+    if verbose:
+        print("List of primes after filtering: %s" % L)
+    return L
 
 def possible_isogeny_degrees(E, algorithm='Billerey', max_l=None,
                              num_l=None, exact=True, verbose=False):
@@ -1434,8 +1452,9 @@ def possible_isogeny_degrees(E, algorithm='Billerey', max_l=None,
         downward split primes: {2, 3}
         downward inert primes: {5}
         primes generating the class group: [2]
-        Complete set of primes: {2, 3, 5}
-        [2, 3, 5]
+        Set of primes before filtering: {2, 3, 5}
+        List of primes after filtering: [2, 3]
+        [2, 3]
     """
     if E.has_cm():
         return isogeny_degrees_cm(E, verbose)


Test patch: