def matrix(self, fill=True): """ Returns the matrix whose entries give the minimal degrees of isogenies between curves in this class. INPUT: - ``fill`` -- boolean (default True). If False then the matrix will contain only zeros and prime entries; if True it will fill in the other degrees. EXAMPLES:: sage: isocls = EllipticCurve('15a3').isogeny_class(use_tuple=False) 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] """ if self._mat is None: self._compute_matrix() mat = self._mat if fill and mat[0,0] == 0: from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix mat = fill_isogeny_matrix(mat) if not fill and mat[0,0] == 1: from sage.schemes.elliptic_curves.ell_curve_isogeny import unfill_isogeny_matrix mat = unfill_isogeny_matrix(mat) return mat
def matrix(self, fill=True): """ Returns the matrix whose entries give the minimal degrees of isogenies between curves in this class. INPUT: - ``fill`` -- boolean (default True). If False then the matrix will contain only zeros and prime entries; if True it will fill in the other degrees. EXAMPLES:: sage: isocls = EllipticCurve('15a3').isogeny_class(use_tuple=False) 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] """ if self._mat is None: self._compute_matrix() mat = self._mat if fill and mat[0, 0] == 0: from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix mat = fill_isogeny_matrix(mat) if not fill and mat[0, 0] == 1: from sage.schemes.elliptic_curves.ell_curve_isogeny import unfill_isogeny_matrix mat = unfill_isogeny_matrix(mat) return mat
def make_graph(M): """ Code extracted from Sage's elliptic curve isogeny class (reshaped in the case maxdegree==12) """ from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix from sage.graphs.graph import Graph n = M.nrows() # = M.ncols() G = Graph(unfill_isogeny_matrix(M), format='weighted_adjacency_matrix') MM = fill_isogeny_matrix(M) # The maximum degree classifies the shape of the isogeny # graph, though the number of vertices is often enough. # This only holds over Q, so this code will need to change # once other isogeny classes are implemented. if n == 1: # one vertex pass elif n == 2: # one edge, two vertices. We align horizontally and put # the lower number on the left vertex. G.set_pos(pos={0:[-0.5,0],1:[0.5,0]}) else: maxdegree = max(max(MM)) if n == 3: # o--o--o centervert = [i for i in range(3) if max(MM.row(i)) < maxdegree][0] other = [i for i in range(3) if i != centervert] G.set_pos(pos={centervert:[0,0],other[0]:[-1,0],other[1]:[1,0]}) elif maxdegree == 4: # o--o<8 centervert = [i for i in range(4) if max(MM.row(i)) < maxdegree][0] other = [i for i in range(4) if i != centervert] G.set_pos(pos={centervert:[0,0],other[0]:[0,1],other[1]:[-0.8660254,-0.5],other[2]:[0.8660254,-0.5]}) elif maxdegree == 27: # o--o--o--o centers = [i for i in range(4) if list(MM.row(i)).count(3) == 2] left = [j for j in range(4) if MM[centers[0],j] == 3 and j not in centers][0] right = [j for j in range(4) if MM[centers[1],j] == 3 and j not in centers][0] G.set_pos(pos={left:[-1.5,0],centers[0]:[-0.5,0],centers[1]:[0.5,0],right:[1.5,0]}) elif n == 4: # square opp = [i for i in range(1,4) if not MM[0,i].is_prime()][0] other = [i for i in range(1,4) if i != opp] G.set_pos(pos={0:[1,1],other[0]:[-1,1],opp:[-1,-1],other[1]:[1,-1]}) elif maxdegree == 8: # 8>o--o<8 centers = [i for i in range(6) if list(MM.row(i)).count(2) == 3] left = [j for j in range(6) if MM[centers[0],j] == 2 and j not in centers] right = [j for j in range(6) if MM[centers[1],j] == 2 and j not in centers] G.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]}) elif maxdegree == 18: # two squares joined on an edge centers = [i for i in range(6) if list(MM.row(i)).count(3) == 2] top = [j for j in range(6) if MM[centers[0],j] == 3] bl = [j for j in range(6) if MM[top[0],j] == 2][0] br = [j for j in range(6) if MM[top[1],j] == 2][0] G.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]}) elif maxdegree == 16: # tree from bottom, 3 regular except for the leaves. centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3] center = [i for i in centers if len([j for j in centers if MM[i,j] == 2]) == 2][0] centers.remove(center) bottom = [j for j in range(8) if MM[center,j] == 2 and j not in centers][0] left = [j for j in range(8) if MM[centers[0],j] == 2 and j != center] right = [j for j in range(8) if MM[centers[1],j] == 2 and j != center] G.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]}) elif maxdegree == 12: # tent centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3] left = [j for j in range(8) if MM[centers[0],j] == 2] right = [] for i in range(3): right.append([j for j in range(8) if MM[centers[1],j] == 2 and MM[left[i],j] == 3][0]) G.set_pos(pos={centers[0]:[-0.3,0],centers[1]:[0.3,0], left[0]:[-0.14,0.15], right[0]:[0.14,0.15], left[1]:[-0.14,-0.15],right[1]:[0.14,-0.15], left[2]:[-0.14,-0.3],right[2]:[0.14,-0.3]}) G.relabel(range(1,n+1)) return G
def _compute(self, verbose=False): """ Computes the list of curves, the matrix and prime-degree isogenies. 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: C2._mat sage: C2._compute() sage: C2._mat [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] sage: C2._compute(verbose=True) possible isogeny degrees: [2, 3] -actual isogeny degrees: {2, 3} -added curve #1 (degree 2)... -added tuple [0, 1, 2]... -added tuple [1, 0, 2]... -added curve #2 (degree 3)... -added tuple [0, 2, 3]... -added tuple [2, 0, 3]...... relevant degrees: [2, 3]... -now completing the isogeny class... -processing curve #1... -added tuple [1, 0, 2]... -added tuple [0, 1, 2]... -added curve #3... -added tuple [1, 3, 3]... -added tuple [3, 1, 3]... -processing curve #2... -added tuple [2, 3, 2]... -added tuple [3, 2, 2]... -added tuple [2, 0, 3]... -added tuple [0, 2, 3]... -processing curve #3... -added tuple [3, 2, 2]... -added tuple [2, 3, 2]... -added tuple [3, 1, 3]... -added tuple [1, 3, 3]...... isogeny class has size 4 Sorting permutation = {0: 1, 1: 2, 2: 0, 3: 3} Matrix = [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] TESTS: Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong):: sage: K.<i> = NumberField(x^2+1) sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146]) sage: C = E.isogeny_class() sage: curves = C.curves sage: isos = C.isogenies() sage: isos[0][3].codomain() == curves[3] True """ from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix from sage.matrix.all import MatrixSpace from sage.sets.set import Set self._maps = None E = self.E.global_minimal_model(semi_global=True) degs = possible_isogeny_degrees(E) if verbose: import sys sys.stdout.write(" possible isogeny degrees: %s" % degs) sys.stdout.flush() isogenies = E.isogenies_prime_degree(degs) if verbose: sys.stdout.write(" -actual isogeny degrees: %s" % Set([phi.degree() for phi in isogenies])) sys.stdout.flush() # Add all new codomains to the list and collect degrees: curves = [E] ncurves = 1 degs = [] # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi tuples = [] def add_tup(t): for T in [t, [t[1],t[0],t[2],0]]: if not T in tuples: tuples.append(T) if verbose: sys.stdout.write(" -added tuple %s..." % T[:3]) sys.stdout.flush() for phi in isogenies: E2 = phi.codomain() d = ZZ(phi.degree()) if not any([E2.is_isomorphic(E3) for E3 in curves]): curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s (degree %s)..." % (ncurves,d)) sys.stdout.flush() add_tup([0,ncurves,d,phi]) ncurves += 1 if not d in degs: degs.append(d) if verbose: sys.stdout.write("... relevant degrees: %s..." % degs) sys.stdout.write(" -now completing the isogeny class...") sys.stdout.flush() i = 1 while i < ncurves: E1 = curves[i] if verbose: sys.stdout.write(" -processing curve #%s..." % i) sys.stdout.flush() isogenies = E1.isogenies_prime_degree(degs) for phi in isogenies: E2 = phi.codomain() d = phi.degree() js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)] if js: # seen codomain already -- up to isomorphism j = js[0] if phi.codomain()!=curves[j]: iso = E2.isomorphism_to(curves[j]) phi.set_post_isomorphism(iso) assert phi.domain()==curves[i] and phi.codomain()==curves[j] add_tup([i,j,d,phi]) else: curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s..." % ncurves) sys.stdout.flush() add_tup([i,ncurves,d,phi]) ncurves += 1 i += 1 if verbose: print("... isogeny class has size %s" % ncurves) # key function for sorting if E.has_rational_cm(): key_function = lambda E: (-E.cm_discriminant(),flatten([list(ai) for ai in E.ainvs()])) else: key_function = lambda E: flatten([list(ai) for ai in E.ainvs()]) self.curves = sorted(curves,key=key_function) perm = dict([(i,self.curves.index(E)) for i,E in enumerate(curves)]) if verbose: print "Sorting permutation = %s" % perm mat = MatrixSpace(ZZ,ncurves)(0) self._maps = [[0]*ncurves for i in range(ncurves)] for i,j,l,phi in tuples: if phi!=0: mat[perm[i],perm[j]] = l self._maps[perm[i]][perm[j]] = phi self._mat = fill_isogeny_matrix(mat) if verbose: print "Matrix = %s" % self._mat if not E.has_rational_cm(): self._qfmat = None return # In the CM case, we will have found some "horizontal" # isogenies of composite degree and would like to replace them # by isogenies of prime degree, mainly to make the isogeny # graph look better. We also construct a matrix whose entries # are not degrees of cyclic isogenies, but rather quadratic # forms (in 1 or 2 variables) representing the isogeny # degrees. For this we take a short cut: properly speaking, # when `\text{End}(E_1)=\text{End}(E_2)=O`, the set # `\text{Hom}(E_1,E_2)` is a rank `1` projective `O`-module, # hence has a well-defined ideal class associated to it, and # hence (using an identification between the ideal class group # and the group of classes of primitive quadratic forms of the # same discriminant) an equivalence class of quadratic forms. # But we currently only care about the numbers represented by # the form, i.e. which genus it is in rather than the exact # class. So it suffices to find one form of the correct # discriminant which represents one isogeny degree from `E_1` # to `E_2` in order to obtain a form which represents all such # degrees. if verbose: print("Creating degree matrix (CM case)") allQs = {} # keys: discriminants d # values: lists of equivalence classes of # primitive forms of discriminant d def find_quadratic_form(d,n): if not d in allQs: from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives allQs[d] = BinaryQF_reduced_representatives(d, primitive_only=True) # now test which of the Qs represents n for Q in allQs[d]: if Q.solve_integer(n): return Q raise ValueError("No form of discriminant %d represents %s" %(d,n)) mat = self._mat qfmat = [[0 for i in range(ncurves)] for j in range(ncurves)] for i, E1 in enumerate(self.curves): for j, E2 in enumerate(self.curves): if j<i: qfmat[i][j] = qfmat[j][i] mat[i,j] = mat[j,i] elif i==j: qfmat[i][j] = [1] # mat[i,j] already 1 else: d = E1.cm_discriminant() if d != E2.cm_discriminant(): qfmat[i][j] = [mat[i,j]] # mat[i,j] already unique else: # horizontal isogeny q = find_quadratic_form(d,mat[i,j]) qfmat[i][j] = list(q) mat[i,j] = q.small_prime_value() self._mat = mat self._qfmat = qfmat if verbose: print("new matrix = %s" % mat) print("matrix of forms = %s" % qfmat)
def _compute(self, verbose=False): """ Computes the list of curves, the matrix and prime-degree isogenies. 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: C2._mat sage: C2._compute() sage: C2._mat [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] sage: C2._compute(verbose=True) possible isogeny degrees: [2, 3] -actual isogeny degrees: {2, 3} -added curve #1 (degree 2)... -added tuple [0, 1, 2]... -added tuple [1, 0, 2]... -added curve #2 (degree 3)... -added tuple [0, 2, 3]... -added tuple [2, 0, 3]...... relevant degrees: [2, 3]... -now completing the isogeny class... -processing curve #1... -added tuple [1, 0, 2]... -added tuple [0, 1, 2]... -added curve #3... -added tuple [1, 3, 3]... -added tuple [3, 1, 3]... -processing curve #2... -added tuple [2, 3, 2]... -added tuple [3, 2, 2]... -added tuple [2, 0, 3]... -added tuple [0, 2, 3]... -processing curve #3... -added tuple [3, 2, 2]... -added tuple [2, 3, 2]... -added tuple [3, 1, 3]... -added tuple [1, 3, 3]...... isogeny class has size 4 Sorting permutation = {0: 1, 1: 2, 2: 0, 3: 3} Matrix = [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] TESTS: Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong):: sage: K.<i> = NumberField(x^2+1) sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146]) sage: C = E.isogeny_class() sage: curves = C.curves sage: isos = C.isogenies() sage: isos[0][3].codomain() == curves[3] True """ from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix from sage.matrix.all import MatrixSpace from sage.sets.set import Set self._maps = None E = self.E.global_minimal_model(semi_global=True) degs = possible_isogeny_degrees(E) if verbose: import sys sys.stdout.write(" possible isogeny degrees: %s" % degs) sys.stdout.flush() isogenies = E.isogenies_prime_degree(degs) if verbose: sys.stdout.write(" -actual isogeny degrees: %s" % Set([phi.degree() for phi in isogenies])) sys.stdout.flush() # Add all new codomains to the list and collect degrees: curves = [E] ncurves = 1 degs = [] # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi tuples = [] def add_tup(t): for T in [t, [t[1],t[0],t[2],0]]: if not T in tuples: tuples.append(T) if verbose: sys.stdout.write(" -added tuple %s..." % T[:3]) sys.stdout.flush() for phi in isogenies: E2 = phi.codomain() d = ZZ(phi.degree()) if not any([E2.is_isomorphic(E3) for E3 in curves]): curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s (degree %s)..." % (ncurves,d)) sys.stdout.flush() add_tup([0,ncurves,d,phi]) ncurves += 1 if not d in degs: degs.append(d) if verbose: sys.stdout.write("... relevant degrees: %s..." % degs) sys.stdout.write(" -now completing the isogeny class...") sys.stdout.flush() i = 1 while i < ncurves: E1 = curves[i] if verbose: sys.stdout.write(" -processing curve #%s..." % i) sys.stdout.flush() isogenies = E1.isogenies_prime_degree(degs) for phi in isogenies: E2 = phi.codomain() d = phi.degree() js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)] if js: # seen codomain already -- up to isomorphism j = js[0] if phi.codomain()!=curves[j]: iso = E2.isomorphism_to(curves[j]) phi.set_post_isomorphism(iso) assert phi.domain()==curves[i] and phi.codomain()==curves[j] add_tup([i,j,d,phi]) else: curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s..." % ncurves) sys.stdout.flush() add_tup([i,ncurves,d,phi]) ncurves += 1 i += 1 if verbose: print("... isogeny class has size %s" % ncurves) # key function for sorting if E.has_rational_cm(): key_function = lambda E: (-E.cm_discriminant(),flatten([list(ai) for ai in E.ainvs()])) else: key_function = lambda E: flatten([list(ai) for ai in E.ainvs()]) self.curves = sorted(curves,key=key_function) perm = dict([(i,self.curves.index(E)) for i,E in enumerate(curves)]) if verbose: print("Sorting permutation = %s" % perm) mat = MatrixSpace(ZZ,ncurves)(0) self._maps = [[0]*ncurves for i in range(ncurves)] for i,j,l,phi in tuples: if phi!=0: mat[perm[i],perm[j]] = l self._maps[perm[i]][perm[j]] = phi self._mat = fill_isogeny_matrix(mat) if verbose: print("Matrix = %s" % self._mat) if not E.has_rational_cm(): self._qfmat = None return # In the CM case, we will have found some "horizontal" # isogenies of composite degree and would like to replace them # by isogenies of prime degree, mainly to make the isogeny # graph look better. We also construct a matrix whose entries # are not degrees of cyclic isogenies, but rather quadratic # forms (in 1 or 2 variables) representing the isogeny # degrees. For this we take a short cut: properly speaking, # when `\text{End}(E_1)=\text{End}(E_2)=O`, the set # `\text{Hom}(E_1,E_2)` is a rank `1` projective `O`-module, # hence has a well-defined ideal class associated to it, and # hence (using an identification between the ideal class group # and the group of classes of primitive quadratic forms of the # same discriminant) an equivalence class of quadratic forms. # But we currently only care about the numbers represented by # the form, i.e. which genus it is in rather than the exact # class. So it suffices to find one form of the correct # discriminant which represents one isogeny degree from `E_1` # to `E_2` in order to obtain a form which represents all such # degrees. if verbose: print("Creating degree matrix (CM case)") allQs = {} # keys: discriminants d # values: lists of equivalence classes of # primitive forms of discriminant d def find_quadratic_form(d,n): if not d in allQs: from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives allQs[d] = BinaryQF_reduced_representatives(d, primitive_only=True) # now test which of the Qs represents n for Q in allQs[d]: if Q.solve_integer(n): return Q raise ValueError("No form of discriminant %d represents %s" %(d,n)) mat = self._mat qfmat = [[0 for i in range(ncurves)] for j in range(ncurves)] for i, E1 in enumerate(self.curves): for j, E2 in enumerate(self.curves): if j<i: qfmat[i][j] = qfmat[j][i] mat[i,j] = mat[j,i] elif i==j: qfmat[i][j] = [1] # mat[i,j] already 1 else: d = E1.cm_discriminant() if d != E2.cm_discriminant(): qfmat[i][j] = [mat[i,j]] # mat[i,j] already unique else: # horizontal isogeny q = find_quadratic_form(d,mat[i,j]) qfmat[i][j] = list(q) mat[i,j] = q.small_prime_value() self._mat = mat self._qfmat = qfmat if verbose: print("new matrix = %s" % mat) print("matrix of forms = %s" % qfmat)