Isogeny class of elliptic curves over number fields#
AUTHORS:
David Roe (2012-03-29) – initial version.
John Cremona (2014-08) – extend to number fields.
- class sage.schemes.elliptic_curves.isogeny_class.IsogenyClass_EC(E, label=None, empty=False)[source]#
Bases:
SageObject
Isogeny class of an elliptic curve.
Note
The current implementation chooses a curve from each isomorphism class in the isogeny class. Over \(\QQ\) this is a unique reduced minimal model in each isomorphism class. Over number fields the model chosen may change in future.
- graph()[source]#
Return a graph whose vertices correspond to curves in this class, and whose edges correspond to prime degree isogenies.
Note
There are only finitely many possible isogeny graphs for curves over \(\QQ\) [Maz1978b]. This function tries to lay out the graph nicely by special casing each isogeny graph. This could also be done over other number fields, such as quadratic fields.
Note
The vertices are labeled 1 to n rather than 0 to n-1 to match LMFDB and Cremona labels for curves over \(\QQ\).
EXAMPLES:
sage: isocls = EllipticCurve('15a3').isogeny_class() sage: G = isocls.graph() sage: sorted(G._pos.items()) [(1, [-0.8660254, 0.5]), (2, [-0.8660254, 1.5]), (3, [-1.7320508, 0]), (4, [0, 0]), (5, [0, -1]), (6, [0.8660254, 0.5]), (7, [0.8660254, 1.5]), (8, [1.7320508, 0])]
>>> from sage.all import * >>> isocls = EllipticCurve('15a3').isogeny_class() >>> G = isocls.graph() >>> sorted(G._pos.items()) [(1, [-0.8660254, 0.5]), (2, [-0.8660254, 1.5]), (3, [-1.7320508, 0]), (4, [0, 0]), (5, [0, -1]), (6, [0.8660254, 0.5]), (7, [0.8660254, 1.5]), (8, [1.7320508, 0])]
- index(C)[source]#
Return the index of a curve in this class.
INPUT:
C
– an elliptic curve in this isogeny class.
OUTPUT:
i
– an integer so that thei
th curve in the class is isomorphic toC
EXAMPLES:
sage: E = EllipticCurve('990j1') sage: iso = E.isogeny_class(order="lmfdb") # orders lexicographically on a-invariants sage: iso.index(E.short_weierstrass_model()) 2
>>> from sage.all import * >>> E = EllipticCurve('990j1') >>> iso = E.isogeny_class(order="lmfdb") # orders lexicographically on a-invariants >>> iso.index(E.short_weierstrass_model()) 2
- isogenies(fill=False)[source]#
Return a list of lists of isogenies and 0s, corresponding to the entries of
matrix()
INPUT:
fill
– boolean (defaultFalse
). Whether to only return prime degree isogenies. Currently only implemented forfill=False
.
OUTPUT:
a list of lists, where the
j
th entry of thei
th list is either zero or a prime degree isogeny from thei
th curve in this class to thej
th curve.
Warning
The domains and codomains of the isogenies will have the same Weierstrass equation as the curves in this class, but they may not be identical python objects in the current implementation.
EXAMPLES:
sage: isocls = EllipticCurve('15a3').isogeny_class() sage: f = isocls.isogenies()[0][1]; f Isogeny of degree 2 from Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field to Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field sage: f.domain() == isocls.curves[0] and f.codomain() == isocls.curves[1] True
>>> from sage.all import * >>> isocls = EllipticCurve('15a3').isogeny_class() >>> f = isocls.isogenies()[Integer(0)][Integer(1)]; f Isogeny of degree 2 from Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field to Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field >>> f.domain() == isocls.curves[Integer(0)] and f.codomain() == isocls.curves[Integer(1)] True
- matrix(fill=True)[source]#
Return the matrix whose entries give the minimal degrees of isogenies between curves in this class.
INPUT:
fill
– boolean (defaultTrue
). IfFalse
then the matrix will contain only zeros and prime entries; ifTrue
it will fill in the other degrees.
EXAMPLES:
sage: isocls = EllipticCurve('15a3').isogeny_class() sage: isocls.matrix() [ 1 2 2 2 4 4 8 8] [ 2 1 4 4 8 8 16 16] [ 2 4 1 4 8 8 16 16] [ 2 4 4 1 2 2 4 4] [ 4 8 8 2 1 4 8 8] [ 4 8 8 2 4 1 2 2] [ 8 16 16 4 8 2 1 4] [ 8 16 16 4 8 2 4 1] sage: isocls.matrix(fill=False) [0 2 2 2 0 0 0 0] [2 0 0 0 0 0 0 0] [2 0 0 0 0 0 0 0] [2 0 0 0 2 2 0 0] [0 0 0 2 0 0 0 0] [0 0 0 2 0 0 2 2] [0 0 0 0 0 2 0 0] [0 0 0 0 0 2 0 0]
>>> from sage.all import * >>> isocls = EllipticCurve('15a3').isogeny_class() >>> isocls.matrix() [ 1 2 2 2 4 4 8 8] [ 2 1 4 4 8 8 16 16] [ 2 4 1 4 8 8 16 16] [ 2 4 4 1 2 2 4 4] [ 4 8 8 2 1 4 8 8] [ 4 8 8 2 4 1 2 2] [ 8 16 16 4 8 2 1 4] [ 8 16 16 4 8 2 4 1] >>> isocls.matrix(fill=False) [0 2 2 2 0 0 0 0] [2 0 0 0 0 0 0 0] [2 0 0 0 0 0 0 0] [2 0 0 0 2 2 0 0] [0 0 0 2 0 0 0 0] [0 0 0 2 0 0 2 2] [0 0 0 0 0 2 0 0] [0 0 0 0 0 2 0 0]
- qf_matrix()[source]#
Return the array whose entries are quadratic forms representing the degrees of isogenies between curves in this class (CM case only).
OUTPUT:
a \(2x2\) array (list of lists) of list, each of the form [2] or [2,1,3] representing the coefficients of an integral quadratic form in 1 or 2 variables whose values are the possible isogeny degrees between the i’th and j’th curve in the class.
EXAMPLES:
sage: pol = PolynomialRing(QQ,'x')([1,0,3,0,1]) sage: K.<c> = NumberField(pol) sage: j = 1480640 + 565760*c^2 sage: E = EllipticCurve(j=j) sage: C = E.isogeny_class() sage: C.qf_matrix() [[[1], [2, 2, 3]], [[2, 2, 3], [1]]]
>>> from sage.all import * >>> pol = PolynomialRing(QQ,'x')([Integer(1),Integer(0),Integer(3),Integer(0),Integer(1)]) >>> K = NumberField(pol, names=('c',)); (c,) = K._first_ngens(1) >>> j = Integer(1480640) + Integer(565760)*c**Integer(2) >>> E = EllipticCurve(j=j) >>> C = E.isogeny_class() >>> C.qf_matrix() [[[1], [2, 2, 3]], [[2, 2, 3], [1]]]
- reorder(order)[source]#
Return a new isogeny class with the curves reordered.
INPUT:
order
– None, a string or an iterable over all curves in this class. Seesage.schemes.elliptic_curves.ell_rational_field.EllipticCurve_rational_field.isogeny_class()
for more details.
OUTPUT:
Another
IsogenyClass_EC
with the curves reordered (and matrices and maps changed as appropriate)
EXAMPLES:
sage: isocls = EllipticCurve('15a1').isogeny_class() sage: print("\n".join(repr(C) for C in isocls.curves)) Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field sage: isocls2 = isocls.reorder('lmfdb') sage: print("\n".join(repr(C) for C in isocls2.curves)) Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field
>>> from sage.all import * >>> isocls = EllipticCurve('15a1').isogeny_class() >>> print("\n".join(repr(C) for C in isocls.curves)) Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field >>> isocls2 = isocls.reorder('lmfdb') >>> print("\n".join(repr(C) for C in isocls2.curves)) Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field
- class sage.schemes.elliptic_curves.isogeny_class.IsogenyClass_EC_NumberField(E, reducible_primes=None, algorithm='Billerey', minimal_models=True)[source]#
Bases:
IsogenyClass_EC
Isogeny classes for elliptic curves over number fields.
- copy()[source]#
Return a copy (mostly used in reordering).
EXAMPLES:
sage: K.<i> = QuadraticField(-1) sage: E = EllipticCurve(K, [0,0,0,0,1]) sage: C = E.isogeny_class() sage: C2 = C.copy() sage: C is C2 False sage: C == C2 True
>>> from sage.all import * >>> K = QuadraticField(-Integer(1), names=('i',)); (i,) = K._first_ngens(1) >>> E = EllipticCurve(K, [Integer(0),Integer(0),Integer(0),Integer(0),Integer(1)]) >>> C = E.isogeny_class() >>> C2 = C.copy() >>> C is C2 False >>> C == C2 True
- class sage.schemes.elliptic_curves.isogeny_class.IsogenyClass_EC_Rational(E, algorithm='sage', label=None, empty=False)[source]#
Bases:
IsogenyClass_EC_NumberField
Isogeny classes for elliptic curves over \(\QQ\).
- copy()[source]#
Return a copy (mostly used in reordering).
EXAMPLES:
sage: E = EllipticCurve('11a1') sage: C = E.isogeny_class() sage: C2 = C.copy() sage: C is C2 False sage: C == C2 True
>>> from sage.all import * >>> E = EllipticCurve('11a1') >>> C = E.isogeny_class() >>> C2 = C.copy() >>> C is C2 False >>> C == C2 True
- sage.schemes.elliptic_curves.isogeny_class.isogeny_degrees_cm(E, verbose=False)[source]#
Return a list of primes \(\ell\) sufficient to generate the isogeny class of \(E\), where \(E\) has CM.
INPUT:
E
– An elliptic curve defined over a number field.
OUTPUT:
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. This list is not necessarily minimal.
ALGORITHM:
For curves with CM by the order \(O\) of discriminant \(d\), the Galois representation is always non-surjective and the curve will admit \(\ell\)-isogenies for infinitely many primes \(\ell\), but there are only finitely many codomains \(E'\). The primes can be divided according to the discriminant \(d'\) of the CM order \(O'\) associated to \(E\): either \(O=O'\), or one contains the other with index \(\ell\), since \(\ell O\subset O'\) and vice versa.
Case (1): \(O=O'\). The degrees of all isogenies between \(E\) and \(E'\) are precisely the integers represented by one of the classes of binary quadratic forms \(Q\) of discriminant \(d\). Hence to obtain all possible isomorphism classes of codomain \(E'\), we need only use one prime \(\ell\) represented by each such class \(Q\). It would in fact suffice to use primes represented by forms which generate the class group. Here we simply omit the principal class and one from each pair of inverse classes, and include a prime represented by each of the remaining forms.
Case (2): \([O':O]=\ell\): so \(d=\ell^2d;\). We include all prime divisors of \(d\).
Case (3): \([O:O']=\ell\): we may assume that \(\ell\) does not divide \(d\) as we have already included these, so \(\ell\) either splits or is inert in \(O\); the class numbers satisfy \(h(O')=(\ell\pm1)h(O)\) accordingly. We include all primes \(\ell\) such that \(\ell\pm1\) divides the degree \([K:\QQ]\).
For curves with only potential CM we proceed as in the CM case, using \(2[K:\QQ]\) instead of \([K:\QQ]\).
EXAMPLES:
For curves with CM by a quadratic order of class number greater than \(1\), we use the structure of the class group to only give one prime in each ideal class:
sage: pol = PolynomialRing(QQ,'x')([1,-3,5,-5,5,-3,1]) sage: L.<a> = NumberField(pol) sage: j = hilbert_class_polynomial(-23).roots(L, multiplicities=False)[0] sage: E = EllipticCurve(j=j) sage: from sage.schemes.elliptic_curves.isogeny_class import isogeny_degrees_cm sage: isogeny_degrees_cm(E, verbose=True) CM case, discriminant = -23 initial primes: {2} upward primes: {} downward ramified primes: {} downward split primes: {2, 3} downward inert primes: {5} primes generating the class group: [2] Set of primes before filtering: {2, 3, 5} List of primes after filtering: [2, 3] [2, 3]
>>> from sage.all import * >>> pol = PolynomialRing(QQ,'x')([Integer(1),-Integer(3),Integer(5),-Integer(5),Integer(5),-Integer(3),Integer(1)]) >>> L = NumberField(pol, names=('a',)); (a,) = L._first_ngens(1) >>> j = hilbert_class_polynomial(-Integer(23)).roots(L, multiplicities=False)[Integer(0)] >>> E = EllipticCurve(j=j) >>> from sage.schemes.elliptic_curves.isogeny_class import isogeny_degrees_cm >>> isogeny_degrees_cm(E, verbose=True) CM case, discriminant = -23 initial primes: {2} upward primes: {} downward ramified primes: {} downward split primes: {2, 3} downward inert primes: {5} primes generating the class group: [2] Set of primes before filtering: {2, 3, 5} List of primes after filtering: [2, 3] [2, 3]
- sage.schemes.elliptic_curves.isogeny_class.possible_isogeny_degrees(E, algorithm='Billerey', max_l=None, num_l=None, exact=True, verbose=False)[source]#
Return a list of primes \(\ell\) sufficient to generate the isogeny class of \(E\).
INPUT:
E
– An elliptic curve defined over a number field.algorithm
(string, default'Billerey'
) – Algorithm to be used for non-CM curves: either'Billerey'
,'Larson'
, or'heuristic'
. Only relevant for non-CM curves and base fields other than \(\QQ\).max_l
(int orNone
) – only relevant for non-CM curves and algorithms'Billerey'
and'heuristic'
. Controls the maximum prime used in either algorithm. IfNone
, use the default for that algorithm.num_l
(int orNone
) – only relevant for non-CM curves and algorithm'Billerey'
. Controls the maximum number of primes used in the algorithm. IfNone
, use the default for that algorithm.exact
(bool, defaultTrue
) – ifTrue
, perform an additional check that the primes returned are all reducible. IfFalse
, skip this step, in which case some of the primes returned may be irreducible.
OUTPUT:
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.
ALGORITHM:
For curves without CM, the set may be taken to be the finite set of primes at which the Galois representation is not surjective, since the existence of an \(\ell\)-isogeny is equivalent to the image of the mod-\(\ell\) Galois representation being contained in a Borel subgroup. Two rigorous algorithms have been implemented to determine this set, due to Larson and Billeray respectively. We also provide a non-rigorous ‘heuristic’ algorithm which only tests reducible primes up to a bound depending on the degree of the base field.
For curves with CM see the documentation for
isogeny_degrees_cm()
.EXAMPLES:
For curves without CM we determine the primes at which the mod \(p\) Galois representation is reducible, i.e. contained in a Borel subgroup:
sage: from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees sage: E = EllipticCurve('11a1') sage: possible_isogeny_degrees(E) [5] sage: possible_isogeny_degrees(E, algorithm='Larson') [5] sage: possible_isogeny_degrees(E, algorithm='Billerey') [5] sage: possible_isogeny_degrees(E, algorithm='heuristic') [5]
>>> from sage.all import * >>> from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees >>> E = EllipticCurve('11a1') >>> possible_isogeny_degrees(E) [5] >>> possible_isogeny_degrees(E, algorithm='Larson') [5] >>> possible_isogeny_degrees(E, algorithm='Billerey') [5] >>> possible_isogeny_degrees(E, algorithm='heuristic') [5]
We check that in this case \(E\) really does have rational \(5\)-isogenies:
sage: [phi.degree() for phi in E.isogenies_prime_degree()] [5, 5]
>>> from sage.all import * >>> [phi.degree() for phi in E.isogenies_prime_degree()] [5, 5]
Over an extension field:
sage: E3 = E.change_ring(CyclotomicField(3)) sage: possible_isogeny_degrees(E3) [5] sage: [phi.degree() for phi in E3.isogenies_prime_degree()] [5, 5]
>>> from sage.all import * >>> E3 = E.change_ring(CyclotomicField(Integer(3))) >>> possible_isogeny_degrees(E3) [5] >>> [phi.degree() for phi in E3.isogenies_prime_degree()] [5, 5]
A higher degree example (LMFDB curve 5.5.170701.1-4.1-b1):
sage: K.<a> = NumberField(x^5 - x^4 - 6*x^3 + 4*x + 1) sage: E = EllipticCurve(K, [a^3 - a^2 - 5*a + 1, a^4 - a^3 - 5*a^2 - a + 1, ....: -a^4 + 2*a^3 + 5*a^2 - 5*a - 3, a^4 - a^3 - 5*a^2 - a, ....: -3*a^4 + 4*a^3 + 17*a^2 - 6*a - 12]) sage: possible_isogeny_degrees(E, algorithm='heuristic') [2] sage: possible_isogeny_degrees(E, algorithm='Billerey') [2] sage: possible_isogeny_degrees(E, algorithm='Larson') [2]
>>> from sage.all import * >>> K = NumberField(x**Integer(5) - x**Integer(4) - Integer(6)*x**Integer(3) + Integer(4)*x + Integer(1), names=('a',)); (a,) = K._first_ngens(1) >>> E = EllipticCurve(K, [a**Integer(3) - a**Integer(2) - Integer(5)*a + Integer(1), a**Integer(4) - a**Integer(3) - Integer(5)*a**Integer(2) - a + Integer(1), ... -a**Integer(4) + Integer(2)*a**Integer(3) + Integer(5)*a**Integer(2) - Integer(5)*a - Integer(3), a**Integer(4) - a**Integer(3) - Integer(5)*a**Integer(2) - a, ... -Integer(3)*a**Integer(4) + Integer(4)*a**Integer(3) + Integer(17)*a**Integer(2) - Integer(6)*a - Integer(12)]) >>> possible_isogeny_degrees(E, algorithm='heuristic') [2] >>> possible_isogeny_degrees(E, algorithm='Billerey') [2] >>> possible_isogeny_degrees(E, algorithm='Larson') [2]
LMFDB curve 4.4.8112.1-108.1-a5:
sage: K.<a> = NumberField(x^4 - 5*x^2 + 3) sage: E = EllipticCurve(K, [a^2 - 2, -a^2 + 3, a^2 - 2, -50*a^2 + 35, 95*a^2 - 67]) sage: possible_isogeny_degrees(E, exact=False, algorithm='Billerey') [2, 5] sage: possible_isogeny_degrees(E, exact=False, algorithm='Larson') [2, 5] sage: possible_isogeny_degrees(E, exact=False, algorithm='heuristic') [2, 5] sage: possible_isogeny_degrees(E) [2, 5]
>>> from sage.all import * >>> K = NumberField(x**Integer(4) - Integer(5)*x**Integer(2) + Integer(3), names=('a',)); (a,) = K._first_ngens(1) >>> E = EllipticCurve(K, [a**Integer(2) - Integer(2), -a**Integer(2) + Integer(3), a**Integer(2) - Integer(2), -Integer(50)*a**Integer(2) + Integer(35), Integer(95)*a**Integer(2) - Integer(67)]) >>> possible_isogeny_degrees(E, exact=False, algorithm='Billerey') [2, 5] >>> possible_isogeny_degrees(E, exact=False, algorithm='Larson') [2, 5] >>> possible_isogeny_degrees(E, exact=False, algorithm='heuristic') [2, 5] >>> possible_isogeny_degrees(E) [2, 5]
This function only returns the primes which are isogeny degrees:
sage: Set(E.isogeny_class().matrix().list()) {1, 2, 4, 5, 20, 10}
>>> from sage.all import * >>> Set(E.isogeny_class().matrix().list()) {1, 2, 4, 5, 20, 10}
For curves with CM by a quadratic order of class number greater than \(1\), we use the structure of the class group to only give one prime in each ideal class:
sage: pol = PolynomialRing(QQ,'x')([1,-3,5,-5,5,-3,1]) sage: L.<a> = NumberField(pol) sage: j = hilbert_class_polynomial(-23).roots(L, multiplicities=False)[0] sage: E = EllipticCurve(j=j) sage: from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees sage: possible_isogeny_degrees(E, verbose=True) CM case, discriminant = -23 initial primes: {2} upward primes: {} downward ramified primes: {} downward split primes: {2, 3} downward inert primes: {5} primes generating the class group: [2] Set of primes before filtering: {2, 3, 5} List of primes after filtering: [2, 3] [2, 3]
>>> from sage.all import * >>> pol = PolynomialRing(QQ,'x')([Integer(1),-Integer(3),Integer(5),-Integer(5),Integer(5),-Integer(3),Integer(1)]) >>> L = NumberField(pol, names=('a',)); (a,) = L._first_ngens(1) >>> j = hilbert_class_polynomial(-Integer(23)).roots(L, multiplicities=False)[Integer(0)] >>> E = EllipticCurve(j=j) >>> from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees >>> possible_isogeny_degrees(E, verbose=True) CM case, discriminant = -23 initial primes: {2} upward primes: {} downward ramified primes: {} downward split primes: {2, 3} downward inert primes: {5} primes generating the class group: [2] Set of primes before filtering: {2, 3, 5} List of primes after filtering: [2, 3] [2, 3]