def spanning_forest(M): r""" Return a list of edges of a spanning forest of the bipartite graph defined by `M` INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row `i` and column `j`. OUTPUT: A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`. EXAMPLES:: sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]]))) 5 sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]]))) 3 """ # Given a matrix, produce a spanning tree G = Graph() m = M.ncols() for (x, y) in M.dict(): G.add_edge(x + m, y) T = [] # find spanning tree in each component for component in G.connected_components(): spanning_tree = kruskal(G.subgraph(component)) for (x, y, z) in spanning_tree: if x < m: t = x x = y y = t T.append((x - m, y)) return T
def spanning_forest(M): r""" Return a list of edges of a spanning forest of the bipartite graph defined by `M` INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row `i` and column `j`. OUTPUT: A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`. EXAMPLES:: sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]]))) 5 sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]]))) 3 """ # Given a matrix, produce a spanning tree G = Graph() m = M.ncols() for (x,y) in M.dict(): G.add_edge(x+m,y) T = [] # find spanning tree in each component for component in G.connected_components(): spanning_tree = kruskal(G.subgraph(component)) for (x,y,z) in spanning_tree: if x < m: t = x x = y y = t T.append((x-m,y)) return T
def spanning_stars(M): r""" Returns the edges of a connected subgraph that is a union of all edges incident some subset of vertices. INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row i and column 0. OUTPUT: A list of tuples `(row,column)` in a spanning forest of the bipartite graph defined by ``M`` EXAMPLES:: sage: edges = sage.matroids.utilities.spanning_stars(matrix([[1,1,1],[1,1,1],[1,1,1]])) sage: Graph([(x+3, y) for x,y in edges]).is_connected() True """ G = Graph() m = M.ncols() for (x, y) in M.dict(): G.add_edge(x + m, y) delta = (M.nrows() + m)**0.5 # remove low degree vertices H = [] # candidate vertices V_0 = set([]) d = 0 while G.order() > 0: (x, d) = min(G.degree_iterator(labels=True), key=itemgetter(1)) if d < delta: V_0.add(x) H.extend(G.edges_incident(x, False)) G.delete_vertex(x) else: break # min degree is at least sqrt(n) # greedily remove vertices G2 = G.copy() # set of picked vertices V_1 = set([]) while G2.order() > 0: # choose vertex with maximum degree in G2 (x, d) = max(G2.degree_iterator(labels=True), key=itemgetter(1)) V_1.add(x) G2.delete_vertices(G2.neighbors(x)) G2.delete_vertex(x) # G2 is a graph of all edges incident to V_1 G2 = Graph() for v in V_1: for u in G.neighbors(v): G2.add_edge(u, v) V = V_0 | V_1 # compute a spanning tree T = spanning_forest(M) for (x, y) in T: if not x in V and not y in V: V.add(v) for v in V: if G.has_vertex(v): # some vertices are not in G H.extend(G.edges_incident(v, False)) # T contain all edges in some spanning tree T = [] for (x, y) in H: if x < m: t = x x = y y = t T.append((x - m, y)) return T
def spanning_stars(M): r""" Returns the edges of a connected subgraph that is a union of all edges incident some subset of vertices. INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row i and column 0. OUTPUT: A list of tuples `(row,column)` in a spanning forest of the bipartite graph defined by ``M`` EXAMPLES:: sage: edges = sage.matroids.utilities.spanning_stars(matrix([[1,1,1],[1,1,1],[1,1,1]])) sage: Graph([(x+3, y) for x,y in edges]).is_connected() True """ G = Graph() m = M.ncols() for (x,y) in M.dict(): G.add_edge(x+m,y) delta = (M.nrows()+m)**0.5 # remove low degree vertices H = [] # candidate vertices V_0 = set([]) d = 0 while G.order()>0: (x,d) = min(G.degree_iterator(labels=True),key=itemgetter(1)) if d < delta: V_0.add(x) H.extend(G.edges_incident(x,False)) G.delete_vertex(x) else: break # min degree is at least sqrt(n) # greedily remove vertices G2 = G.copy() # set of picked vertices V_1 = set([]) while G2.order()>0: # choose vertex with maximum degree in G2 (x,d) = max(G2.degree_iterator(labels=True),key=itemgetter(1)) V_1.add(x) G2.delete_vertices(G2.neighbors(x)) G2.delete_vertex(x) # G2 is a graph of all edges incident to V_1 G2 = Graph() for v in V_1: for u in G.neighbors(v): G2.add_edge(u,v) V = V_0 | V_1 # compute a spanning tree T = spanning_forest(M) for (x,y) in T: if not x in V and not y in V: V.add(v) for v in V: if G.has_vertex(v): # some vertices are not in G H.extend(G.edges_incident(v,False)) # T contain all edges in some spanning tree T = [] for (x,y) in H: if x < m: t = x x = y y = t T.append((x-m,y)) return T
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)