class Isogeny_graph: # Class for construction isogeny graphs with 1 or more degrees # ls is a list of primes which define the defining degrees def __init__(self, E, ls, special=False): self._ls = ls j = E.j_invariant() self.field = E.base_field() self._graph = Graph(multiedges=True, loops=True) self._special = False self._graph.add_vertex(j) queue = [j] R = rainbow(len(ls) + 1) self._rainbow = R self._edge_colors = {R[i]: [] for i in range(len(ls))} while queue: color = 0 s = queue.pop(0) if s == 0 or s == 1728: self._special = True for l in ls: neighb = isogenous_curves(s, l) for i in neighb: if not self._graph.has_vertex(i[0]): queue.append(i[0]) self._graph.add_vertex(i[0]) if not ((s, i[0], l) in self._edge_colors[R[color]] or (i[0], s, l) in self._edge_colors[R[color]]): for _ in range(i[1]): self._graph.add_edge((s, i[0], l)) self._edge_colors[R[color]].append((s, i[0], l)) color += 1 if self._special and special: print("Curve with j_invariant 0 or 1728 found, may malfunction.") def __repr__(self): return "Isogeny graph of degrees %r" % (self._ls) # Returns degrees of isogenies def degrees(self): return self._ls # Returns list of all edges def edges(self): return self._graph.edges() # Returns list of all vertices def vertices(self): return self._graph.vertices() # Plots the graph # Optional arguments figsize, vertex_size, vertex_labels and layout which are the same as in igraph def plot(self, figsize=None, edge_labels=False, vertex_size=None, layout=None): if vertex_size == None: return self._graph.plot(edge_colors=self._edge_colors, figsize=figsize, edge_labels=edge_labels, layout=layout) else: return self._graph.plot(edge_colors=self._edge_colors, figsize=figsize, edge_labels=edge_labels, vertex_size=vertex_size, layout=layout)
class Volcano: # Class for l-volcano of elliptic curve E over finite field # We can construct Volcano either using Velu algorithm (Velu = True) or modular polynomials (Velu = False) # If Velu algorithm is chosen, constructor tries to find kernels of isogenies in extension, # if the size of the base field of extension is higher than upper_bit_limit, then the algorithm stops # In case of the option of modular polynomials, we assume that l<127 # You can turn off the 0,1728 warning with special = False def __init__(self, E, l, Velu=False, upper_bit_limit=100, special=True): self._l = l self._E = E global VELU VELU = Velu global UPPER_BITS UPPER_BITS = upper_bit_limit self.field = E.base_field() try: self._neighbours, self._vertices, self._special = BFS( E.j_invariant(), l) except large_extension_error as e: print( "Upper limit for bitlength of size of extension field exceeded" ) print(e.msg) return if self._special and special: print("Curve with j_invariant 0 or 1728 found, may malfunction.") self._depths = {} self._levels = [] self._depth = 0 self._graph = Graph(multiedges=True, loops=True) for s in self._neighbours.values(): self._graph.add_vertex(s[0]) for s in self._neighbours.values(): for i in range(1, len(s)): if not self._graph.has_edge(s[0], s[i][0]): for j in range(s[i][1]): self._graph.add_edge((s[0], s[i][0], j)) if len(self._vertices) > 1 and E.is_ordinary(): self.compute_depths() else: self._depths = {str(E.j_invariant()): 0} self._levels = [self._vertices] # Method for computing depths and sorting vertices into levels def compute_depths(self): heights = {} level = [] if len(list(self._neighbours.values())[0]) == 3: self._levels = [self._vertices] self._depths = {str(i): 0 for i in self._vertices} self._depth = 0 return for s in self._neighbours.keys(): if len(self._neighbours[s]) == 2: heights[s] = 0 level.append(self._neighbours[s][0]) self._levels.append(level) h = 1 while len(heights.keys()) != len(self._vertices): level = [] for s in self._neighbours.keys(): if s in heights.keys(): continue if len(self._neighbours[s]) > 2: if str(self._neighbours[s][1][0]) in heights.keys( ) and heights[str(self._neighbours[s][1][0])] == h - 1: heights[s] = h level.append(self._neighbours[s][0]) continue if str(self._neighbours[s][2][0]) in heights.keys( ) and heights[str(self._neighbours[s][2][0])] == h - 1: heights[s] = h level.append(self._neighbours[s][0]) continue if len(self._neighbours[s]) > 3: if str(self._neighbours[s][3][0]) in heights.keys( ) and heights[str(self._neighbours[s][3][0])] == h - 1: heights[s] = h level.append(self._neighbours[s][0]) continue h += 1 self._levels.append(level) self._depth = h - 1 self._depths = {} for k in heights.keys(): self._depths[k] = h - 1 - heights[k] self._levels.reverse() # Returns the defining degree of volcano def degree(self): return self._l # Returns the level at depth i def level(self, i): return self._levels[i] # Returns list of all edges def edges(self): return self._graph.edges() # Returns depth of volcano def depth(self): return self._depth # Returns the crater of volcano def crater(self): return self._levels[0] # Plots the volcano # Optional arguments figsize, vertex_size, vertex_labels and layout which are the same as in igraph (sage Class) def plot(self, figsize=None, vertex_labels=True, vertex_size=None, layout=None): try: self._graph.layout(layout=layout, save_pos=True) except: pass if vertex_size != None: return self._graph.plot(figsize=figsize, vertex_labels=vertex_labels, vertex_size=vertex_size) else: return self._graph.plot(figsize=figsize, vertex_labels=vertex_labels) # Returns all vertices of volcano def vertices(self): return self._vertices # Returns all neighbours of vertex j def neighbors(self, j): return self._neighbours[str(j)][1:] # Returns depth of vertex j def vertex_depth(self, j): return self._depths[str(j)] # Returns true if the volcano contains vertex 1728 or 0 def special(self): return self._special # Returns parent (upper level neighbour) of j def volcano_parent(self, j): h = self._depths[str(j)] for i in self._neighbours[str(j)][1:]: if self._depths[str(i[0])] < h: return i[0] return None # Returns all children (lower level neighbours) of vertex j def volcano_children(self, j): children = [] h = self._depths[str(j)] for i in self._neighbours[str(j)][1:]: if self._depths[str(i[0])] > h: children.append(i[0]) return children # Finds an extension of curve over which the volcano has depth h def expand_volcano(self, h): return Volcano(expand_volcano(self._E, h, self._l), self._l) def __repr__(self): return "Isogeny %r-volcano of depth %r over %r" % ( self._l, self.depth(), self.field)