Path: blob/master/src/sage/schemes/elliptic_curves/isogeny_class.py
8821 views
"""1This file defines a class for an isogeny class of an elliptic curve.23AUTHORS:45David Roe (2012-03-29) -- initial version.6"""78##############################################################################9# Copyright (C) 2012 David Roe <[email protected]>10# William Stein <[email protected]>11#12# Distributed under the terms of the GNU General Public License (GPL)13#14# This code is distributed in the hope that it will be useful,15# but WITHOUT ANY WARRANTY; without even the implied warranty of16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU17# General Public License for more details.18#19# The full text of the GPL is available at:20#21# http://www.gnu.org/licenses/22##############################################################################2324from sage.structure.sage_object import SageObject25from sage.misc.lazy_attribute import lazy_attribute26import constructor27import sage.databases.cremona28from sage.rings.all import ZZ29from sage.misc.cachefunc import cached_method30from sage.misc.abstract_method import abstract_method31from sage.schemes.elliptic_curves.ell_rational_field import EllipticCurve_rational_field3233class IsogenyClass_EC(SageObject):34"""35Isogeny class of an elliptic curve.3637The current implementation chooses a curve from each isomorphism38class in the isogeny class, since over `\QQ` there is a unique39reduced minimal model in each isomorphism class.4041EXAMPLES::424344"""45def __init__(self, E, label=None, empty=False):46"""47Over `\QQ` we use curves since minimal models exist and there is a canonical choice of one.4849INPUT:5051- ``label`` -- string or None, a Cremona or LMFDB label, used52in printing5354EXAMPLES::5556sage: cls = EllipticCurve('1011b1').isogeny_class()57sage: print "\n".join([repr(E) for E in cls.curves])58Elliptic Curve defined by y^2 + x*y = x^3 - 8*x - 9 over Rational Field59Elliptic Curve defined by y^2 + x*y = x^3 - 23*x + 30 over Rational Field60"""61self.E = E62self._label = label63if not empty:64self._compute()6566def __len__(self):67"""68The length is just the number of curves in the class.6970EXAMPLES::7172sage: E = EllipticCurve('15a')73sage: len(E.isogeny_class()) # indirect doctest74875"""76return len(self.curves)7778def __iter__(self):79"""80Iterator over curves in the class.8182EXAMPLES::8384sage: E = EllipticCurve('15a')85sage: all(C.conductor() == 15 for C in E.isogeny_class()) # indirect doctest86True87"""88return iter(self.curves)8990def __getitem__(self, i):91"""92Gets the `i`th curve in the class.9394EXAMPLES::9596sage: E = EllipticCurve('990j1')97sage: iso = E.isogeny_class(order="lmfdb") # orders lexicographically on a-invariants98sage: iso[2] == E # indirect doctest99True100"""101return self.curves[i]102103def index(self, C):104"""105Returns the index of a curve in this class.106107INPUT:108109- ``C`` -- an elliptic curve in this isogeny class.110111OUTPUT:112113- ``i`` -- an integer so that the ``i``th curve in the class114is isomorphic to ``C``115116EXAMPLES::117118sage: E = EllipticCurve('990j1')119sage: iso = E.isogeny_class(order="lmfdb") # orders lexicographically on a-invariants120sage: iso.index(E.short_weierstrass_model())1212122"""123# This will need updating once we start talking about curves over more general number fields124if not isinstance(C, EllipticCurve_rational_field):125raise ValueError("x not in isogeny class")126return self.curves.index(C.minimal_model())127128def __cmp__(self, other):129"""130Returns 0 if self and other are the same isogeny class.131132If they are different, compares the sorted underlying lists of133curves.134135Note that two isogeny classes with different orderings will136compare as the same. If you want to include the ordering,137just compare the list of curves.138139EXAMPLES::140141sage: E = EllipticCurve('990j1')142sage: EE = EllipticCurve('990j4')143sage: E.isogeny_class() == EE.isogeny_class() # indirect doctest144True145"""146# This will need updating once we start talking about curves over more general number fields147if isinstance(other, IsogenyClass_EC):148return cmp(sorted(self.curves), sorted(other.curves))149return cmp(type(self), type(other))150151def __hash__(self):152"""153Hash is based on the a-invariants of the sorted list of154minimal models.155156EXAMPLES::157158sage: E = EllipticCurve('990j1')159sage: C = E.isogeny_class()160sage: hash(C) == hash(tuple(sorted([curve.a_invariants() for curve in C.curves]))) # indirect doctest161True162"""163try:164return self._hash165except AttributeError:166self._hash = hash(tuple(sorted([E.a_invariants() for E in self.curves])))167return self._hash168169def _repr_(self):170"""171The string representation depends on whether an LMFDB or172Cremona label for the curve is known when this isogeny class173is constructed.174175EXAMPLES:176177If the curve is constructed from an LMFDB label then that178label is used::179180sage: E = EllipticCurve('462.f3')181sage: E.isogeny_class() # indirect doctest182Elliptic curve isogeny class 462.f183184If the curve is constructed from a Cremona label then that185label is used::186187sage: E = EllipticCurve('990j1')188sage: E.isogeny_class()189Elliptic curve isogeny class 990j190191Otherwise the representation is determined from the first192curve in the class::193194sage: E = EllipticCurve([1,2,3,4,5])195sage: E.isogeny_class()196Isogeny class of Elliptic Curve defined by y^2 + x*y = x^3 - x^2 + 4*x + 3 over Rational Field197"""198if self._label:199return "Elliptic curve isogeny class %s"%(self._label)200else:201return "Isogeny class of %r"%(self.curves[0])202203def __contains__(self, x):204"""205INPUT:206207- ``x`` -- a Python object.208209OUTPUT:210211- boolean -- True iff ``x`` is an elliptic curve in this isogeny class.212213EXAMPLES::214215sage: cls = EllipticCurve('15a3').isogeny_class()216sage: E = EllipticCurve('15a7'); E in cls217True218sage: E.short_weierstrass_model() in cls219True220"""221if not isinstance(x, EllipticCurve_rational_field):222return False223return x.minimal_model() in self.curves224225@cached_method226def matrix(self, fill=True):227"""228Returns the matrix whose entries give the minimal degrees of229isogenies between curves in this class.230231INPUT:232233- ``fill`` -- boolean (default True). If False then the234matrix will contain only zeros and prime entries; if True it235will fill in the other degrees.236237EXAMPLES::238239sage: isocls = EllipticCurve('15a3').isogeny_class()240sage: isocls.matrix()241[ 1 2 2 2 4 4 8 8]242[ 2 1 4 4 8 8 16 16]243[ 2 4 1 4 8 8 16 16]244[ 2 4 4 1 2 2 4 4]245[ 4 8 8 2 1 4 8 8]246[ 4 8 8 2 4 1 2 2]247[ 8 16 16 4 8 2 1 4]248[ 8 16 16 4 8 2 4 1]249sage: isocls.matrix(fill=False)250[0 2 2 2 0 0 0 0]251[2 0 0 0 0 0 0 0]252[2 0 0 0 0 0 0 0]253[2 0 0 0 2 2 0 0]254[0 0 0 2 0 0 0 0]255[0 0 0 2 0 0 2 2]256[0 0 0 0 0 2 0 0]257[0 0 0 0 0 2 0 0]258"""259if self._mat is None:260self._compute_matrix()261mat = self._mat262if fill and mat[0,0] == 0:263from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix264mat = fill_isogeny_matrix(mat)265if not fill and mat[0,0] == 1:266from sage.schemes.elliptic_curves.ell_curve_isogeny import unfill_isogeny_matrix267mat = unfill_isogeny_matrix(mat)268return mat269270@cached_method271def isogenies(self, fill=False):272"""273Returns a list of lists of isogenies and 0s, corresponding to the entries of :meth:`matrix`274275INPUT:276277- ``fill`` -- boolean (default False). Whether to only return278prime degree isogenies. Currently only implemented for279``fill=False``.280281OUTPUT:282283- a list of lists, where the ``j``th entry of the ``i``th list284is either zero or a prime degree isogeny from ``i``th curve285in this class to the ``j``th curve.286287WARNING:288289- The domains and codmains of the isogenies will have the same290Weierstrass equation as the curves in this class, but they291may not be identical python objects in the current292implementation.293294EXAMPLES::295296sage: isocls = EllipticCurve('15a3').isogeny_class()297sage: f = isocls.isogenies()[0][1]; f298Isogeny 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 Field299sage: f.domain() == isocls.curves[0] and f.codomain() == isocls.curves[1]300True301"""302if fill:303raise NotImplementedError304isogenies = self._maps305if isogenies is None:306self._compute_isogenies()307isogenies = self._maps308return isogenies309310@cached_method311def graph(self):312"""313Returns a graph whose vertices correspond to curves in this class, and whose edges correspond to prime degree isogenies.314315.. note:316317There are only finitely many possible isogeny graphs for318curves over `\QQ` [M78]. This function tries to lay out the graph319nicely by special casing each isogeny graph.320321.. note:322323The vertices are labeled 1 to n rather than 0 to n-1 to324correspond to LMFDB and Cremona labels.325326EXAMPLES::327328sage: isocls = EllipticCurve('15a3').isogeny_class()329sage: G = isocls.graph()330sage: sorted(G._pos.items())331[(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])]332333REFERENCES:334335.. [M78] B. Mazur. Rational Isogenies of Prime Degree.336*Inventiones mathematicae* 44,129-162 (1978).337"""338from sage.graphs.graph import Graph339M = self.matrix(fill = False)340n = M.nrows() # = M.ncols()341G = Graph(M, format='weighted_adjacency_matrix')342N = self.matrix(fill = True)343D = dict([(v,self.curves[v]) for v in G.vertices()])344# The maximum degree classifies the shape of the isogeny345# graph, though the number of vertices is often enough.346# This only holds over Q, so this code will need to change347# once other isogeny classes are implemented.348if n == 1:349# one vertex350pass351elif n == 2:352# one edge, two vertices. We align horizontally and put353# the lower number on the left vertex.354G.set_pos(pos={0:[-0.5,0],1:[0.5,0]})355else:356maxdegree = max(max(N))357if n == 3:358# o--o--o359centervert = [i for i in range(3) if max(N.row(i)) < maxdegree][0]360other = [i for i in range(3) if i != centervert]361G.set_pos(pos={centervert:[0,0],other[0]:[-1,0],other[1]:[1,0]})362elif maxdegree == 4:363# o--o<8364centervert = [i for i in range(4) if max(N.row(i)) < maxdegree][0]365other = [i for i in range(4) if i != centervert]366G.set_pos(pos={centervert:[0,0],other[0]:[0,1],other[1]:[-0.8660254,-0.5],other[2]:[0.8660254,-0.5]})367elif maxdegree == 27:368# o--o--o--o369centers = [i for i in range(4) if list(N.row(i)).count(3) == 2]370left = [j for j in range(4) if N[centers[0],j] == 3 and j not in centers][0]371right = [j for j in range(4) if N[centers[1],j] == 3 and j not in centers][0]372G.set_pos(pos={left:[-1.5,0],centers[0]:[-0.5,0],centers[1]:[0.5,0],right:[1.5,0]})373elif n == 4:374# square375opp = [i for i in range(1,4) if not N[0,i].is_prime()][0]376other = [i for i in range(1,4) if i != opp]377G.set_pos(pos={0:[1,1],other[0]:[-1,1],opp:[-1,-1],other[1]:[1,-1]})378elif maxdegree == 8:379# 8>o--o<8380centers = [i for i in range(6) if list(N.row(i)).count(2) == 3]381left = [j for j in range(6) if N[centers[0],j] == 2 and j not in centers]382right = [j for j in range(6) if N[centers[1],j] == 2 and j not in centers]383G.set_pos(pos={centers[0]:[-0.5,0],left[0]:[-1,0.8660254],left[1]:[-1,-0.8660254],centers[1]:[0.5,0],right[0]:[1,0.8660254],right[1]:[1,-0.8660254]})384elif maxdegree == 18:385# two squares joined on an edge386centers = [i for i in range(6) if list(N.row(i)).count(3) == 2]387top = [j for j in range(6) if N[centers[0],j] == 3]388bl = [j for j in range(6) if N[top[0],j] == 2][0]389br = [j for j in range(6) if N[top[1],j] == 2][0]390G.set_pos(pos={centers[0]:[0,0.5],centers[1]:[0,-0.5],top[0]:[-1,0.5],top[1]:[1,0.5],bl:[-1,-0.5],br:[1,-0.5]})391elif maxdegree == 16:392# tree from bottom, 3 regular except for the leaves.393centers = [i for i in range(8) if list(N.row(i)).count(2) == 3]394center = [i for i in centers if len([j for j in centers if N[i,j] == 2]) == 2][0]395centers.remove(center)396bottom = [j for j in range(8) if N[center,j] == 2 and j not in centers][0]397left = [j for j in range(8) if N[centers[0],j] == 2 and j != center]398right = [j for j in range(8) if N[centers[1],j] == 2 and j != center]399G.set_pos(pos={center:[0,0],bottom:[0,-1],centers[0]:[-0.8660254,0.5],centers[1]:[0.8660254,0.5],left[0]:[-0.8660254,1.5],right[0]:[0.8660254,1.5],left[1]:[-1.7320508,0],right[1]:[1.7320508,0]})400elif maxdegree == 12:401# tent402centers = [i for i in range(8) if list(N.row(i)).count(2) == 3]403left = [j for j in range(8) if N[centers[0],j] == 2]404right = []405for i in range(3):406right.append([j for j in range(8) if N[centers[1],j] == 2 and N[left[i],j] == 3][0])407G.set_pos(pos={centers[0]:[-0.75,0],centers[1]:[0.75,0],left[0]:[-0.75,1],right[0]:[0.75,1],left[1]:[-1.25,-0.75],right[1]:[0.25,-0.75],left[2]:[-0.25,-0.25],right[2]:[1.25,-0.25]})408G.set_vertices(D)409G.relabel(range(1,n+1))410return G411412@cached_method413def reorder(self, order):414"""415Return a new isogeny class with the curves reordered.416417INPUT:418419- ``order`` -- None, a string or an iterable over all curves420in this class. See421:meth:`sage.schemes.elliptic_curves.ell_rational_field.EllipticCurve_rational_field.isogeny_class`422for more details.423424OUTPUT:425426- Another :class:`IsogenyClass_EC` with the curves reordered427(and matrices and maps changed as appropriate)428429EXAMPLES::430431sage: isocls = EllipticCurve('15a1').isogeny_class()432sage: print "\n".join([repr(C) for C in isocls.curves])433Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field434Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field435Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field436Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field437Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field438Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field439Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field440Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field441sage: isocls2 = isocls.reorder('lmfdb')442sage: print "\n".join([repr(C) for C in isocls2.curves])443Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 2160*x - 39540 over Rational Field444Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 135*x - 660 over Rational Field445Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 110*x - 880 over Rational Field446Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 80*x + 242 over Rational Field447Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 10*x - 10 over Rational Field448Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 - 5*x + 2 over Rational Field449Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 over Rational Field450Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field451"""452if order is None or isinstance(order, basestring) and order == self._algorithm:453return self454if isinstance(order, basestring):455if order == "lmfdb":456reordered_curves = sorted(self.curves, key = lambda E: E.a_invariants())457else:458reordered_curves = list(self.E.isogeny_class(algorithm=order))459elif isinstance(order, (list, tuple, IsogenyClass_EC)):460reordered_curves = list(order)461if len(reordered_curves) != len(self.curves):462raise ValueError("Incorrect length")463else:464raise TypeError("order parameter should be a string, list of curves or isogeny class")465need_perm = self._mat is not None466cpy = self.copy()467curves = []468perm = []469for E in reordered_curves:470try:471j = self.curves.index(E)472except ValueError:473try:474j = self.curves.index(E.minimal_model())475except ValueError:476raise ValueError("order does not yield a permutation of curves")477curves.append(self.curves[j])478if need_perm: perm.append(j+1)479cpy.curves = tuple(curves)480if need_perm:481from sage.groups.perm_gps.permgroup_named import SymmetricGroup482perm = SymmetricGroup(len(self.curves))(perm)483cpy._mat = perm.matrix() * self._mat * (~perm).matrix()484if self._maps is not None:485n = len(self._maps)486cpy._maps = [self._maps[perm(i+1)-1] for i in range(n)]487for i in range(n):488cpy._maps[i] = [cpy._maps[i][perm(j+1)-1] for j in range(n)]489else:490cpy._mat = None491cpy._maps = None492return cpy493494@abstract_method495def _compute(self):496"""497This function is called during initialization and should fill498in ``self.curves``, and possibly ``self._mat`` and499``self._maps`` as well, depending on the algorithm.500501.. note:502503This is currently redundant; it used to be required when mwrank was used.504"""505pass506507@abstract_method508def _compute_matrix(self):509"""510For algorithms that don't compute the isogeny matrix at first,511this function should fill in ``self._mat``.512513EXAMPLES::514515sage: EllipticCurve('11a1').isogeny_class('database').matrix() # indirect doctest516[ 1 5 5]517[ 5 1 25]518[ 5 25 1]519"""520pass521522@abstract_method523def _compute_isogenies(self):524"""525For algorithms that don't compute the isogenies at first, this526function should fill in ``self._maps``.527528.. note:529530This is currently redundant; it used to be required when mwrank was used.531"""532pass533534class IsogenyClass_EC_Rational(IsogenyClass_EC):535"""536Isogeny classes for elliptic curves over `\QQ`.537"""538def __init__(self, E, algorithm="sage", label=None, empty=False):539"""540INPUT:541542- ``E`` -- an elliptic curve over Q.543544- ``algorithm`` -- a string (default "sage"). One of the545following:546547- "sage" -- Use sage's implementation to compute the curves,548matrix and isogenies549550- "database" -- Use the Cremona database (only works if the551curve is in the database)552553- ``label`` -- a string, the label of this isogeny class554(e.g. '15a' or '37.b'). Used in printing.555556- ``empty`` -- don't compute the curves right now (used when reordering)557558EXAMPLES::559560sage: isocls = EllipticCurve('389a1').isogeny_class(); isocls561Elliptic curve isogeny class 389a562sage: E = EllipticCurve([0, 0, 0, 0, 1001]) # conductor 108216108563sage: E.isogeny_class(order='database')564Traceback (most recent call last):565...566RuntimeError: unable to to find Elliptic Curve defined by y^2 = x^3 + 1001 over Rational Field in the database567sage: TestSuite(isocls).run()568"""569self._algorithm = algorithm570IsogenyClass_EC.__init__(self, E, label=label, empty=empty)571572def copy(self):573"""574Returns a copy (mostly used in reordering).575576EXAMPLES::577578sage: isocls = EllipticCurve('389a1').isogeny_class()579sage: isocls2 = isocls.copy()580sage: isocls is isocls2581False582sage: isocls == isocls2583True584"""585ans = IsogenyClass_EC_Rational(self.E, self._algorithm, self._label, empty=True)586# The following isn't needed internally, but it will keep587# things from breaking if this is used for something other588# than reordering.589ans.curves = self.curves590ans._mat = None591ans._maps = None592return ans593594def _compute(self):595"""596Computes the list of curves, and possibly the matrix and597prime-degree isogenies (depending on the algorithm selected).598599EXAMPLES::600601sage: isocls = EllipticCurve('48a1').isogeny_class('sage').copy()602sage: isocls._mat603sage: isocls._compute(); isocls._mat604[0 2 2 2 0 0]605[2 0 0 0 2 2]606[2 0 0 0 0 0]607[2 0 0 0 0 0]608[0 2 0 0 0 0]609[0 2 0 0 0 0]610"""611algorithm = self._algorithm612from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix613from sage.matrix.all import MatrixSpace614self._maps = None615if algorithm == "database":616try:617label = self.E.cremona_label(space=False)618except RuntimeError:619raise RuntimeError("unable to to find %s in the database"%self.E)620db = sage.databases.cremona.CremonaDatabase()621curves = db.isogeny_class(label)622if len(curves) == 0:623raise RuntimeError("unable to to find %s in the database"%self.E)624# All curves will have the same conductor and isogeny class,625# and there are are most 8 of them, so lexicographic sorting is okay.626self.curves = tuple(sorted(curves, key = lambda E: E.cremona_label()))627self._mat = None628elif algorithm == "sage":629curves = [self.E.minimal_model()]630ijl_triples = []631l_list = None632i = 0633while i<len(curves):634E = curves[i]635isogs = E.isogenies_prime_degree(l_list)636for phi in isogs:637Edash = phi.codomain()638l = phi.degree()639# look to see if Edash is new. Note that the640# curves returned by isogenies_prime_degree() are641# standard minimal models, so it suffices to check642# equality rather than isomorphism here.643try:644j = curves.index(Edash)645except ValueError:646j = len(curves)647curves.append(Edash)648ijl_triples.append((i,j,l,phi))649if l_list is None:650l_list = [l for l in set([ZZ(f.degree()) for f in isogs])]651i = i+1652self.curves = tuple(curves)653ncurves = len(curves)654self._mat = MatrixSpace(ZZ,ncurves)(0)655self._maps = [[0]*ncurves for i in range(ncurves)]656for i,j,l,phi in ijl_triples:657self._mat[i,j] = l658self._maps[i][j]=phi659else:660raise ValueError, "unknown algorithm '%s'"%algorithm661662def _compute_matrix(self):663"""664Computes the matrix, assuming that the list of curves is computed.665666EXAMPLES::667668sage: isocls = EllipticCurve('1225h1').isogeny_class('database')669sage: isocls._mat670sage: isocls._compute_matrix(); isocls._mat671[ 0 37]672[37 0]673"""674self._mat = self.E.isogeny_class(order=self.curves)._mat675676def _compute_isogenies(self):677"""678EXAMPLES::679680sage: E = EllipticCurve('15a1')681sage: isocls = E.isogeny_class()682sage: maps = isocls.isogenies() # indirect doctest683sage: f = maps[0][1]684sage: f.domain() == isocls[0] and f.codomain() == isocls[1]685True686"""687recomputed = self.E.isogeny_class(order=self.curves)688self._mat = recomputed._mat689# The domains and codomains here will be equal, but not the same Python object.690self._maps = recomputed._maps691692693