def Circuit(self, n): r""" Returns the circuit on `n` vertices The circuit is an oriented ``CycleGraph`` EXAMPLE: A circuit is the smallest strongly connected digraph:: sage: circuit = digraphs.Circuit(15) sage: len(circuit.strongly_connected_components()) == 1 True """ g = DiGraph(n) g.name("Circuit") if n == 0: return g elif n == 1: g.allow_loops(True) g.add_edge(0, 0) return g else: g.add_edges([(i, i + 1) for i in xrange(n - 1)]) g.add_edge(n - 1, 0) return g
def basis_of_simple_closed_curves(self): from sage.graphs.digraph import DiGraph from sage.all import randint n = self._origami.nb_squares() C = self.chain_space() G = DiGraph(multiedges=True, loops=True, implementation='c_graph') for i in xrange(2 * n): G.add_edge(self._starts[i], self._ends[i], i) waiting = [0] gens = [] reps = [None] * G.num_verts() reps[0] = C.zero() while waiting: x = waiting.pop(randint(0, len(waiting) - 1)) for v0, v1, e in G.outgoing_edges(x): if reps[v1] is not None: gens.append(reps[v0] + C.gen(e) - reps[v1]) else: reps[v1] = reps[v0] + C.gen(e) waiting.append(v1) return gens
def Tournament(self,n): r""" Returns a tournament on `n` vertices. In this tournament there is an edge from `i` to `j` if `i<j`. INPUT: - ``n`` (integer) -- number of vertices in the tournament. EXAMPLES:: sage: g = digraphs.Tournament(5) sage: g.vertices() [0, 1, 2, 3, 4] sage: g.size() 10 sage: g.automorphism_group().cardinality() 1 """ if n<0: raise ValueError("The number of vertices must be a positive integer.") g = DiGraph() g.name("Tournament on "+str(n)+" vertices") for i in range(n-1): for j in range(i+1, n): g.add_edge(i,j) if n: from sage.graphs.graph_plot import _circle_embedding _circle_embedding(g, range(n)) return g
def RandomDirectedGNP(self, n, p): r""" Returns a random digraph on `n` nodes. Each edge is inserted independently with probability `p`. REFERENCES: - [1] P. Erdos and A. Renyi, On Random Graphs, Publ. Math. 6, 290 (1959). - [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959). PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. EXAMPLE:: sage: D = digraphs.RandomDirectedGNP(10, .2) sage: D.num_verts() 10 sage: D.edges(labels=False) [(0, 1), (0, 3), (0, 6), (0, 8), (1, 4), (3, 7), (4, 1), (4, 8), (5, 2), (5, 6), (5, 8), (6, 4), (7, 6), (8, 4), (8, 5), (8, 7), (8, 9), (9, 3), (9, 4), (9, 6)] """ from sage.misc.prandom import random D = DiGraph(n) for i in xrange(n): for j in xrange(i): if random() < p: D.add_edge(i,j) for j in xrange(i+1,n): if random() < p: D.add_edge(i,j) return D
def inclusion_digraph(self): r""" Returns the class inclusion digraph Upon the first call, this loads the database from the local XML file. Subsequent calls are cached. EXAMPLES:: sage: g = graph_classes.inclusion_digraph(); g Digraph on ... vertices """ classes = self.classes() inclusions = self.inclusions() from sage.graphs.digraph import DiGraph inclusion_digraph = DiGraph() inclusion_digraph.add_vertices(classes.keys()) for edge in inclusions: if edge.get("confidence","") == "unpublished": continue inclusion_digraph.add_edge(edge['super'], edge['sub']) return inclusion_digraph
def Circuit(self,n): r""" Returns the circuit on `n` vertices The circuit is an oriented ``CycleGraph`` EXAMPLE: A circuit is the smallest strongly connected digraph:: sage: circuit = digraphs.Circuit(15) sage: len(circuit.strongly_connected_components()) == 1 True """ if n<0: raise ValueError("The number of vertices must be a positive integer.") g = DiGraph() g.name("Circuit on "+str(n)+" vertices") if n==0: return g elif n == 1: g.allow_loops(True) g.add_edge(0,0) return g else: g.add_edges([(i,i+1) for i in xrange(n-1)]) g.add_edge(n-1,0) return g
def basis_of_simple_closed_curves(self): from sage.graphs.digraph import DiGraph from sage.all import randint n = self._origami.nb_squares() C = self.chain_space() G = DiGraph(multiedges=True,loops=True,implementation='c_graph') for i in xrange(2*n): G.add_edge(self._starts[i], self._ends[i], i) waiting = [0] gens = [] reps = [None] * G.num_verts() reps[0] = C.zero() while waiting: x = waiting.pop(randint(0,len(waiting)-1)) for v0,v1,e in G.outgoing_edges(x): if reps[v1] is not None: gens.append(reps[v0] + C.gen(e) - reps[v1]) else: reps[v1] = reps[v0] + C.gen(e) waiting.append(v1) return gens
def Circuit(self, n): r""" Returns the circuit on `n` vertices The circuit is an oriented ``CycleGraph`` EXAMPLE: A circuit is the smallest strongly connected digraph:: sage: circuit = digraphs.Circuit(15) sage: len(circuit.strongly_connected_components()) == 1 True """ if n < 0: raise ValueError( "The number of vertices must be a positive integer.") g = DiGraph() g.name("Circuit on " + str(n) + " vertices") if n == 0: return g elif n == 1: g.allow_loops(True) g.add_edge(0, 0) return g else: g.add_edges([(i, i + 1) for i in xrange(n - 1)]) g.add_edge(n - 1, 0) return g
def Circuit(self,n): r""" Returns the circuit on `n` vertices The circuit is an oriented ``CycleGraph`` EXAMPLE: A circuit is the smallest strongly connected digraph:: sage: circuit = digraphs.Circuit(15) sage: len(circuit.strongly_connected_components()) == 1 True """ g = DiGraph(n) g.name("Circuit") if n==0: return g elif n == 1: g.allow_loops(True) g.add_edge(0,0) return g else: g.add_edges([(i,i+1) for i in xrange(n-1)]) g.add_edge(n-1,0) return g
def digraph(self, index_set=None): r""" Return the :class:`DiGraph` associated to ``self``. EXAMPLES:: sage: B = crystals.Letters(['A', [1,3]]) sage: G = B.digraph(); G Multi-digraph on 6 vertices sage: Q = crystals.Letters(['Q',3]) sage: G = Q.digraph(); G Multi-digraph on 3 vertices sage: G.edges() [(1, 2, -1), (1, 2, 1), (2, 3, -2), (2, 3, 2)] The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, green for edge 3, and dashed with the corresponding color for barred edges. Edge 0 is dotted black:: sage: view(G) # optional - dot2tex graphviz, not tested (opens external window) """ from sage.graphs.digraph import DiGraph from sage.misc.latex import LatexExpr from sage.combinat.root_system.cartan_type import CartanType if index_set is None: index_set = self.index_set() G = DiGraph(multiedges=True) G.add_vertices(self) for i in index_set: for x in G: y = x.f(i) if y is not None: G.add_edge(x, y, i) def edge_options(data): u, v, l = data edge_opts = {'edge_string': '->', 'color': 'black'} if l > 0: edge_opts['color'] = CartanType._colors.get(l, 'black') edge_opts['label'] = LatexExpr(str(l)) elif l < 0: edge_opts[ 'color'] = "dashed," + CartanType._colors.get( -l, 'black') edge_opts['label'] = LatexExpr("\\overline{%s}" % str(-l)) else: edge_opts[ 'color'] = "dotted," + CartanType._colors.get( l, 'black') edge_opts['label'] = LatexExpr(str(l)) return edge_opts G.set_latex_options(format="dot2tex", edge_labels=True, edge_options=edge_options) return G
def random_orientation(G): r""" Return a random orientation of a graph `G`. An *orientation* of an undirected graph is a directed graph such that every edge is assigned a direction. Hence there are `2^m` oriented digraphs for a simple graph with `m` edges. INPUT: - ``G`` -- a Graph. EXAMPLES:: sage: from sage.graphs.orientations import random_orientation sage: G = graphs.PetersenGraph() sage: D = random_orientation(G) sage: D.order() == G.order(), D.size() == G.size() (True, True) TESTS: Giving anything else than a Graph:: sage: random_orientation([]) Traceback (most recent call last): ... ValueError: the input parameter must be a Graph .. SEEALSO:: - :meth:`~Graph.orientations` """ from sage.graphs.graph import Graph if not isinstance(G, Graph): raise ValueError("the input parameter must be a Graph") D = DiGraph(data=[G.vertices(), []], format='vertices_and_edges', multiedges=G.allows_multiple_edges(), loops=G.allows_loops(), weighted=G.weighted(), pos=G.get_pos(), name="Random orientation of {}".format(G.name()) ) if hasattr(G, '_embedding'): D._embedding = copy(G._embedding) from sage.misc.prandom import getrandbits rbits = getrandbits(G.size()) for u,v,l in G.edge_iterator(): if rbits % 2: D.add_edge(u, v, l) else: D.add_edge(v, u, l) rbits >>= 1 return D
def min_hasse_diagram(self): r""" Return Hasse diagram of the trace. OUTPUT: Directed graph of generator indexes. .. SEEALSO:: :meth:`~sage.monoids.trace_monoid.TraceMonoidElement.hasse_digram`, :meth:`~sage.monoids.trace_monoid.TraceMonoidElement.naive_hasse_diagram`. EXAMPLES:: sage: from sage.monoids.trace_monoid import TraceMonoid sage: I = (('a','d'), ('d','a'), ('b','c'), ('c','b')) sage: M.<a,b,c,d> = TraceMonoid(I=I) sage: x = b * a * d * a * c * b sage: x.min_hasse_diagram() Digraph on 6 vertices """ elements = self._flat_elements() elements.reverse() independence = self.parent()._independence reachable = dict() min = set() graph = DiGraph({}) for i, x in enumerate(elements): reachable[i] = set() front = min.copy() while front: used = set() for j in list(front): y = elements[j] if (x, y) not in independence: graph.add_edge(i, j) reachable[i].add(j) reachable[i].update(reachable[j]) if j in min: min.remove(j) used.add(j) forbidden = set(chain.from_iterable(reachable[v] for v in used)) front = set( dest for _, dest in graph.outgoing_edges(front, labels=False)) front = front - forbidden min.add(i) length = len(elements) graph.relabel(length - 1 - i for i in range(length)) return graph
def as_graph(self): r""" Return the graph associated to self """ from sage.graphs.digraph import DiGraph G = DiGraph(multiedges=True, loops=True) d = self.degree() g = [self.g_tuple(i) for i in range(4)] for i in range(d): for j in range(4): G.add_edge(i, g[j][i], j) return G
def as_graph(self): r""" Return the graph associated to self """ from sage.graphs.digraph import DiGraph G = DiGraph(multiedges=True,loops=True) d = self.degree() g = [self.g_tuple(i) for i in xrange(4)] for i in xrange(d): for j in xrange(4): G.add_edge(i,g[j][i],j) return G
def uncompactify(self): r""" Returns the tree obtained from self by splitting edges so that they are labelled by exactly one letter. The resulting tree is isomorphic to the suffix trie. EXAMPLES:: sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree, SuffixTrie sage: abbab = Words("ab")("abbab") sage: s = SuffixTrie(abbab) sage: t = ImplicitSuffixTree(abbab) sage: t.uncompactify().is_isomorphic(s.to_digraph()) True """ tree = self.to_digraph(word_labels=True) newtree = DiGraph() newtree.add_vertices(range(tree.order())) new_node = tree.order() + 1 for (u, v, label) in tree.edge_iterator(): if len(label) == 1: newtree.add_edge(u, v) else: newtree.add_edge(u, new_node, label[0]) for w in label[1:-1]: newtree.add_edge(new_node, new_node + 1, w) new_node += 1 newtree.add_edge(new_node, v, label[-1]) new_node += 1 return newtree
def digraph(self): r""" Return the :class:`DiGraph` associated to ``self``. EXAMPLES:: sage: B = crystals.Letters(['A', [1,3]]) sage: G = B.digraph(); G Multi-digraph on 6 vertices sage: Q = crystals.Letters(['Q',3]) sage: G = Q.digraph(); G Multi-digraph on 3 vertices sage: G.edges() [(1, 2, -1), (1, 2, 1), (2, 3, -2), (2, 3, 2)] The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, green for edge 3, and dashed with the corresponding color for barred edges. Edge 0 is dotted black:: sage: view(G) # optional - dot2tex graphviz, not tested (opens external window) """ from sage.graphs.digraph import DiGraph from sage.misc.latex import LatexExpr from sage.combinat.root_system.cartan_type import CartanType G = DiGraph(multiedges=True) G.add_vertices(self) for i in self.index_set(): for x in G: y = x.f(i) if y is not None: G.add_edge(x, y, i) def edge_options(data): u, v, l = data edge_opts = { 'edge_string': '->', 'color': 'black' } if l > 0: edge_opts['color'] = CartanType._colors.get(l, 'black') edge_opts['label'] = LatexExpr(str(l)) elif l < 0: edge_opts['color'] = "dashed," + CartanType._colors.get(-l, 'black') edge_opts['label'] = LatexExpr("\\overline{%s}" % str(-l)) else: edge_opts['color'] = "dotted," + CartanType._colors.get(l, 'black') edge_opts['label'] = LatexExpr(str(l)) return edge_opts G.set_latex_options(format="dot2tex", edge_labels=True, edge_options=edge_options) return G
def uncompactify(self): r""" Returns the tree obtained from self by splitting edges so that they are labelled by exactly one letter. The resulting tree is isomorphic to the suffix trie. EXAMPLES:: sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree, SuffixTrie sage: abbab = Words("ab")("abbab") sage: s = SuffixTrie(abbab) sage: t = ImplicitSuffixTree(abbab) sage: t.uncompactify().is_isomorphic(s.to_digraph()) True """ tree = self.to_digraph(word_labels=True) newtree = DiGraph() newtree.add_vertices(range(tree.order())) new_node = tree.order() + 1 for (u,v,label) in tree.edge_iterator(): if len(label) == 1: newtree.add_edge(u,v) else: newtree.add_edge(u,new_node,label[0]); for w in label[1:-1]: newtree.add_edge(new_node,new_node+1,w) new_node += 1 newtree.add_edge(new_node,v,label[-1]) new_node += 1 return newtree
def add_edge(self, i, j, label=1): """ EXAMPLES:: sage: from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class sage: d = DynkinDiagram_class(CartanType(['A',3])) sage: list(sorted(d.edges())) [] sage: d.add_edge(2, 3) sage: list(sorted(d.edges())) [(2, 3, 1), (3, 2, 1)] """ DiGraph.add_edge(self, i, j, label) if not self.has_edge(j,i): self.add_edge(j,i,1)
def add_edge(self, i, j, label=1): """ EXAMPLES:: sage: from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class sage: d = DynkinDiagram_class(CartanType(['A',3])) sage: list(sorted(d.edges())) [] sage: d.add_edge(2, 3) sage: list(sorted(d.edges())) [(2, 3, 1), (3, 2, 1)] """ DiGraph.add_edge(self, i, j, label) if not self.has_edge(j, i): self.add_edge(j, i, 1)
def plot(self, **kwargs): """ Return a graphics object representing the Kontsevich graph. INPUT: - ``edge_labels`` (boolean, default True) -- if True, show edge labels. - ``indices`` (boolean, default False) -- if True, show indices as edge labels instead of L and R; see :meth:`._latex_`. - ``upright`` (boolean, default False) -- if True, try to plot the graph with the ground vertices on the bottom and the rest on top. """ if not 'edge_labels' in kwargs: kwargs['edge_labels'] = True # show edge labels by default if 'indices' in kwargs: del kwargs['indices'] KG = DiGraph(self) for (k, e) in enumerate(self.edges()): KG.delete_edge(e) KG.add_edge((e[0], e[1], chr(97 + k))) return KG.plot(**kwargs) if len(self.ground_vertices()) == 2 and 'upright' in kwargs: del kwargs['upright'] kwargs['save_pos'] = True DiGraph.plot(self, **kwargs) positions = self.get_pos() # translate F to origin: F_pos = vector(positions[self.ground_vertices()[0]]) for p in positions: positions[p] = list(vector(positions[p]) - F_pos) # scale F - G distance to 1: G_len = abs(vector(positions[self.ground_vertices()[1]])) for p in positions: positions[p] = list(vector(positions[p]) / G_len) # rotate the vector F - G to (1,0) from math import atan2 theta = -atan2(positions[self.ground_vertices()[1]][1], positions[self.ground_vertices()[1]][0]) for p in positions: positions[p] = list( matrix([[cos(theta), -(sin(theta))], [sin(theta), cos(theta)]]) * vector(positions[p])) # flip if most things are below the x-axis: if len([(x, y) for (x, y) in positions.values() if y < 0]) / len( self.internal_vertices()) > 0.5: for p in positions: positions[p] = [positions[p][0], -positions[p][1]] return DiGraph.plot(self, **kwargs)
def plot(self, **kwds): """ EXAMPLES:: sage: X = gauge_theories.threeSU(3) sage: print X.plot().description() """ g = DiGraph(loops=True, sparse=True, multiedges=True) for G in self._gauge_groups: g.add_vertex(G) for field in self._fields: if isinstance(field, FieldBifundamental): g.add_edge(field.src, field.dst, field) if isinstance(field, FieldAdjoint): g.add_edge(field.node, field.node, field) return g.plot(vertex_labels=True, edge_labels=True, graph_border=True)
def quiver_v2(self): #if hasattr(self, "_quiver_cache"): # return self._quiver_cache from sage.combinat.subset import Subsets from sage.graphs.digraph import DiGraph Q = DiGraph(multiedges=True) Q.add_vertices(self.j_transversal()) g = self.associated_graph() for U in Subsets(g.vertices()): for W in Subsets(U): h = g.subgraph(U.difference(W)) n = h.connected_components_number() - 1 if n > 0: u = self.j_class_representative(self.j_class_index(self(''.join(U)))) w = self.j_class_representative(self.j_class_index(self(''.join(W)))) for i in range(n): Q.add_edge(w, u, i) return Q
def plot(self, **kwargs): """ Return a graphics object representing the Kontsevich graph. INPUT: - ``edge_labels`` (boolean, default True) -- if True, show edge labels. - ``indices`` (boolean, default False) -- if True, show indices as edge labels instead of L and R; see :meth:`._latex_`. - ``upright`` (boolean, default False) -- if True, try to plot the graph with the ground vertices on the bottom and the rest on top. """ if not 'edge_labels' in kwargs: kwargs['edge_labels'] = True # show edge labels by default if 'indices' in kwargs: del kwargs['indices'] KG = DiGraph(self) for (k,e) in enumerate(self.edges()): KG.delete_edge(e) KG.add_edge((e[0], e[1], chr(97 + k))) return KG.plot(**kwargs) if len(self.ground_vertices()) == 2 and 'upright' in kwargs: del kwargs['upright'] kwargs['save_pos'] = True DiGraph.plot(self, **kwargs) positions = self.get_pos() # translate F to origin: F_pos = vector(positions[self.ground_vertices()[0]]) for p in positions: positions[p] = list(vector(positions[p]) - F_pos) # scale F - G distance to 1: G_len = abs(vector(positions[self.ground_vertices()[1]])) for p in positions: positions[p] = list(vector(positions[p])/G_len) # rotate the vector F - G to (1,0) from math import atan2 theta = -atan2(positions[self.ground_vertices()[1]][1], positions[self.ground_vertices()[1]][0]) for p in positions: positions[p] = list(matrix([[cos(theta),-(sin(theta))],[sin(theta),cos(theta)]]) * vector(positions[p])) # flip if most things are below the x-axis: if len([(x,y) for (x,y) in positions.values() if y < 0])/len(self.internal_vertices()) > 0.5: for p in positions: positions[p] = [positions[p][0], -positions[p][1]] return DiGraph.plot(self, **kwargs)
def RandomTournament(self, n): r""" Returns a random tournament on `n` vertices. For every pair of vertices, the tournament has an edge from `i` to `j` with probability `1/2`, otherwise it has an edge from `j` to `i`. See :wikipedia:`Tournament_(graph_theory)` INPUT: - ``n`` (integer) -- number of vertices. EXAMPLES:: sage: T = digraphs.RandomTournament(10); T Random Tournament: Digraph on 10 vertices sage: T.size() == binomial(10, 2) True sage: digraphs.RandomTournament(-1) Traceback (most recent call last): ... ValueError: The number of vertices cannot be strictly negative! """ from sage.misc.prandom import random g = DiGraph(n) g.name("Random Tournament") for i in range(n - 1): for j in range(i + 1, n): if random() <= 0.5: g.add_edge(i, j) else: g.add_edge(j, i) if n: from sage.graphs.graph_plot import _circle_embedding _circle_embedding(g, range(n)) return g
def TransitiveTournament(self, n): r""" Returns a transitive tournament on `n` vertices. In this tournament there is an edge from `i` to `j` if `i<j`. See :wikipedia:`Tournament_(graph_theory)` INPUT: - ``n`` (integer) -- number of vertices in the tournament. EXAMPLES:: sage: g = digraphs.TransitiveTournament(5) sage: g.vertices() [0, 1, 2, 3, 4] sage: g.size() 10 sage: g.automorphism_group().cardinality() 1 TESTS:: sage: digraphs.TransitiveTournament(-1) Traceback (most recent call last): ... ValueError: The number of vertices cannot be strictly negative! """ g = DiGraph(n) g.name("Transitive Tournament") for i in range(n - 1): for j in range(i + 1, n): g.add_edge(i, j) if n: from sage.graphs.graph_plot import _circle_embedding _circle_embedding(g, range(n)) return g
def RandomTournament(self, n): r""" Returns a random tournament on `n` vertices. For every pair of vertices, the tournament has an edge from `i` to `j` with probability `1/2`, otherwise it has an edge from `j` to `i`. See :wikipedia:`Tournament_(graph_theory)` INPUT: - ``n`` (integer) -- number of vertices. EXAMPLES:: sage: T = digraphs.RandomTournament(10); T Random Tournament: Digraph on 10 vertices sage: T.size() == binomial(10, 2) True sage: digraphs.RandomTournament(-1) Traceback (most recent call last): ... ValueError: The number of vertices cannot be strictly negative! """ from sage.misc.prandom import random g = DiGraph(n) g.name("Random Tournament") for i in range(n - 1): for j in range(i + 1, n): if random() <= .5: g.add_edge(i, j) else: g.add_edge(j, i) if n: from sage.graphs.graph_plot import _circle_embedding _circle_embedding(g, range(n)) return g
def to_dag(self): """ Returns a directed acyclic graph corresponding to the skew partition. EXAMPLES:: sage: dag = SkewPartition([[3, 2, 1], [1, 1]]).to_dag() sage: dag.edges() [('0,1', '0,2', None), ('0,1', '1,1', None)] sage: dag.vertices() ['0,1', '0,2', '1,1', '2,0'] """ i = 0 #Make the skew tableau from the shape skew = [[1]*row_length for row_length in self.outer()] inner = self.inner() for i in range(len(inner)): for j in range(inner[i]): skew[i][j] = None G = DiGraph() for row in range(len(skew)): for column in range(len(skew[row])): if skew[row][column] is not None: string = "%d,%d" % (row, column) G.add_vertex(string) #Check to see if there is a node to the right if column != len(skew[row]) - 1: newstring = "%d,%d" % (row, column+1) G.add_edge(string, newstring) #Check to see if there is anything below if row != len(skew) - 1: if len(skew[row+1]) > column: if skew[row+1][column] is not None: newstring = "%d,%d" % (row+1, column) G.add_edge(string, newstring) return G
def _digraph(self): r""" Constructs the underlying digraph and stores the result as an attribute. EXAMPLES:: sage: from sage.combinat.yang_baxter_graph import SwapIncreasingOperator sage: ops = [SwapIncreasingOperator(i) for i in range(2)] sage: Y = YangBaxterGraph(root=(1,2,3), operators=ops) sage: Y._digraph Digraph on 6 vertices """ digraph = DiGraph() digraph.add_vertex(self._root) queue = [self._root] while queue: u = queue.pop() for (v, l) in self._succesors(u): if v not in digraph: queue.append(v) digraph.add_edge(u, v, l) return digraph
def _digraph(self): r""" Constructs the underlying digraph and stores the result as an attribute. EXAMPLES:: sage: from sage.combinat.yang_baxter_graph import SwapIncreasingOperator sage: ops = [SwapIncreasingOperator(i) for i in range(2)] sage: Y = YangBaxterGraph(root=(1,2,3), operators=ops) sage: Y._digraph Digraph on 6 vertices """ digraph = DiGraph() digraph.add_vertex(self._root) queue = [self._root] while queue: u = queue.pop() for (v, l) in self._successors(u): if v not in digraph: queue.append(v) digraph.add_edge(u, v, l) return digraph
def induced_orientation(self, w): r""" The induced subgraph of the complement of the underlying graph with an orientation determined by `w`: an edge `(x,y)` is directed from `x` to `y` if `x` comes before `y` in `w`. EXAMPLES:: sage: from sage_semigroups.monoids.free_partially_commutative_left_regular_band import FreePartiallyCommutativeLeftRegularBand sage: G = Graph({'a':['b'],'b':['d'],'c':[],'d':[]}) sage: S = FreePartiallyCommutativeLeftRegularBand(G); S Free partially commutative left regular band on Graph on 4 vertices sage: w = S('cdab') sage: H = S.induced_orientation(w) sage: H.vertices() ['a', 'b', 'c', 'd'] sage: H.edges() [('c', 'a', None), ('c', 'b', None), ('c', 'd', None), ('d', 'a', None)] sage: w = S('dab') sage: H = S.induced_orientation(w) sage: H.vertices() ['a', 'b', 'd'] sage: H.edges() [('d', 'a', None)] """ pos = dict((wi,i) for (i,wi) in enumerate(w.value)) D = DiGraph() D.add_vertices(pos) for (u,v,l) in self.associated_graph().complement().edges(): if u in pos and v in pos: if pos[u] < pos[v]: D.add_edge(u,v) else: D.add_edge(v,u) return D
def Tournament(self, n): r""" Returns a tournament on `n` vertices. In this tournament there is an edge from `i` to `j` if `i<j`. INPUT: - ``n`` (integer) -- number of vertices in the tournament. EXAMPLES:: sage: g = digraphs.Tournament(5) sage: g.vertices() [0, 1, 2, 3, 4] sage: g.size() 10 sage: g.automorphism_group().cardinality() 1 """ if n < 0: raise ValueError( "The number of vertices must be a positive integer.") g = DiGraph() g.name("Tournament on " + str(n) + " vertices") for i in range(n - 1): for j in range(i + 1, n): g.add_edge(i, j) if n: from sage.graphs.graph_plot import _circle_embedding _circle_embedding(g, range(n)) return g
def Kautz(self, k, D, vertices='strings'): r""" Returns the Kautz digraph of degree `d` and diameter `D`. The Kautz digraph has been defined in [Kautz68]_. The Kautz digraph of degree `d` and diameter `D` has `d^{D-1}(d+1)` vertices. This digraph is build upon a set of vertices equal to the set of words of length `D` from an alphabet of `d+1` letters such that consecutive letters are differents. There is an arc from vertex `u` to vertex `v` if `v` can be obtained from `u` by removing the leftmost letter and adding a new letter, distinct from the rightmost letter of `u`, at the right end. The Kautz digraph of degree `d` and diameter `D` is isomorphic to the digraph of Imase and Itoh [II83]_ of degree `d` and order `d^{D-1}(d+1)`. See also the :wikipedia:`Wikipedia article on Kautz Graphs <Kautz_graph>`. INPUTS: - ``k`` -- Two possibilities for this parameter : - An integer equal to the degree of the digraph to be produced, that is the cardinality minus one of the alphabet to use. - An iterable object to be used as the set of letters. The degree of the resulting digraph is the cardinality of the set of letters minus one. - ``D`` -- An integer equal to the diameter of the digraph, and also to the length of a vertex label when ``vertices == 'strings'``. - ``vertices`` -- 'strings' (default) or 'integers', specifying whether the vertices are words build upon an alphabet or integers. EXAMPLES:: sage: K = digraphs.Kautz(2, 3) sage: K.is_isomorphic(digraphs.ImaseItoh(12, 2), certify = True) (True, {'010': 0, '012': 1, '020': 3, '021': 2, '101': 11, '102': 10, '120': 9, '121': 8, '201': 5, '202': 4, '210': 6, '212': 7}) sage: K = digraphs.Kautz([1,'a','B'], 2) sage: K.edges() [('1B', 'B1', '1'), ('1B', 'Ba', 'a'), ('1a', 'a1', '1'), ('1a', 'aB', 'B'), ('B1', '1B', 'B'), ('B1', '1a', 'a'), ('Ba', 'a1', '1'), ('Ba', 'aB', 'B'), ('a1', '1B', 'B'), ('a1', '1a', 'a'), ('aB', 'B1', '1'), ('aB', 'Ba', 'a')] sage: K = digraphs.Kautz([1,'aA','BB'], 2) sage: K.edges() [('1,BB', 'BB,1', '1'), ('1,BB', 'BB,aA', 'aA'), ('1,aA', 'aA,1', '1'), ('1,aA', 'aA,BB', 'BB'), ('BB,1', '1,BB', 'BB'), ('BB,1', '1,aA', 'aA'), ('BB,aA', 'aA,1', '1'), ('BB,aA', 'aA,BB', 'BB'), ('aA,1', '1,BB', 'BB'), ('aA,1', '1,aA', 'aA'), ('aA,BB', 'BB,1', '1'), ('aA,BB', 'BB,aA', 'aA')] TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.Kautz(0, 2) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for degree at least one. sage: G = digraphs.Kautz(['a'], 2) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for degree at least one. An exception is raised when the diameter of the graph is less than one:: sage: G = digraphs.Kautz(2, 0) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for diameter at least one. REFERENCE: .. [Kautz68] W. H. Kautz. Bounds on directed (d, k) graphs. Theory of cellular logic networks and machines, AFCRL-68-0668, SRI Project 7258, Final Rep., pp. 20-28, 1968. """ if D < 1: raise ValueError( "Kautz digraphs are defined for diameter at least one.") from sage.combinat.words.words import Words from sage.rings.integer import Integer my_alphabet = Words( [str(i) for i in range(k + 1)] if isinstance(k, Integer) else k, 1) if my_alphabet.size_of_alphabet() < 2: raise ValueError( "Kautz digraphs are defined for degree at least one.") if vertices == 'strings': # We start building the set of vertices V = [i for i in my_alphabet] for i in range(D - 1): VV = [] for w in V: VV += [w * a for a in my_alphabet if not w.has_suffix(a)] V = VV # We now build the set of arcs G = DiGraph() for u in V: for a in my_alphabet: if not u.has_suffix(a): G.add_edge(u.string_rep(), (u[1:] * a).string_rep(), a.string_rep()) else: d = my_alphabet.size_of_alphabet() - 1 G = digraphs.ImaseItoh((d + 1) * (d**(D - 1)), d) G.name("Kautz digraph (k=%s, D=%s)" % (k, D)) return G
def RandomPoset(n, p): r""" Generate a random poset on ``n`` elements according to a probability ``p``. INPUT: - ``n`` - number of elements, a non-negative integer - ``p`` - a probability, a real number between 0 and 1 (inclusive) OUTPUT: A poset on `n` elements. The probability `p` roughly measures width/height of the output: `p=0` always generates an antichain, `p=1` will return a chain. To create interesting examples, keep the probability small, perhaps on the order of `1/n`. EXAMPLES:: sage: set_random_seed(0) # Results are reproducible sage: P = Posets.RandomPoset(5, 0.3) sage: P.cover_relations() [[5, 4], [4, 2], [1, 2]] TESTS:: sage: Posets.RandomPoset('junk', 0.5) Traceback (most recent call last): ... TypeError: number of elements must be an integer, not junk sage: Posets.RandomPoset(-6, 0.5) Traceback (most recent call last): ... ValueError: number of elements must be non-negative, not -6 sage: Posets.RandomPoset(6, 'garbage') Traceback (most recent call last): ... TypeError: probability must be a real number, not garbage sage: Posets.RandomPoset(6, -0.5) Traceback (most recent call last): ... ValueError: probability must be between 0 and 1, not -0.5 sage: Posets.RandomPoset(0, 0.5) Finite poset containing 0 elements """ from sage.misc.prandom import random try: n = Integer(n) except TypeError: raise TypeError("number of elements must be an integer, not {0}".format(n)) if n < 0: raise ValueError("number of elements must be non-negative, not {0}".format(n)) try: p = float(p) except Exception: raise TypeError("probability must be a real number, not {0}".format(p)) if p < 0 or p> 1: raise ValueError("probability must be between 0 and 1, not {0}".format(p)) D = DiGraph(loops=False, multiedges=False) D.add_vertices(range(n)) for i in range(n): for j in range(i+1, n): if random() < p: D.add_edge(i, j) D.relabel(list(Permutations(n).random_element())) return Poset(D, cover_relations=False)
def line_graph(self, labels=True): """ Returns the line graph of the (di)graph. INPUT: - ``labels`` (boolean) -- whether edge labels should be taken in consideration. If ``labels=True``, the vertices of the line graph will be triples ``(u,v,label)``, and pairs of vertices otherwise. This is set to ``True`` by default. The line graph of an undirected graph G is an undirected graph H such that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G. In other words, an edge in H represents a path of length 2 in G. The line graph of a directed graph G is a directed graph H such that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G and the terminal vertex of e is the initial vertex of f. In other words, an edge in H represents a (directed) path of length 2 in G. .. NOTE:: As a :class:`Graph` object only accepts hashable objects as vertices (and as the vertices of the line graph are the edges of the graph), this code will fail if edge labels are not hashable. You can also set the argument ``labels=False`` to ignore labels. .. SEEALSO:: - The :mod:`line_graph <sage.graphs.line_graph>` module. - :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs` -- the forbidden subgraphs of a line graph. - :meth:`~Graph.is_line_graph` -- tests whether a graph is a line graph. EXAMPLES:: sage: g = graphs.CompleteGraph(4) sage: h = g.line_graph() sage: h.vertices() [(0, 1, None), (0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None), (2, 3, None)] sage: h.am() [0 1 1 1 1 0] [1 0 1 1 0 1] [1 1 0 0 1 1] [1 1 0 0 1 1] [1 0 1 1 0 1] [0 1 1 1 1 0] sage: h2 = g.line_graph(labels=False) sage: h2.vertices() [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] sage: h2.am() == h.am() True sage: g = DiGraph([[1..4],lambda i,j: i<j]) sage: h = g.line_graph() sage: h.vertices() [(1, 2, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None), (3, 4, None)] sage: h.edges() [((1, 2, None), (2, 3, None), None), ((1, 2, None), (2, 4, None), None), ((1, 3, None), (3, 4, None), None), ((2, 3, None), (3, 4, None), None)] Tests: :trac:`13787`:: sage: g = graphs.KneserGraph(7,1) sage: C = graphs.CompleteGraph(7) sage: C.is_isomorphic(g) True sage: C.line_graph().is_isomorphic(g.line_graph()) True """ self._scream_if_not_simple() if self._directed: from sage.graphs.digraph import DiGraph G = DiGraph() G.add_vertices(self.edges(labels=labels)) for v in self: # Connect appropriate incident edges of the vertex v G.add_edges([(e,f) for e in self.incoming_edge_iterator(v, labels=labels) \ for f in self.outgoing_edge_iterator(v, labels=labels)]) return G else: from sage.graphs.all import Graph G = Graph() # We must sort the edges' endpoints so that (1,2,None) is seen as # the same edge as (2,1,None). # # We do so by comparing hashes, just in case all the natural order # (<) on vertices would not be a total order (for instance when # vertices are sets). If two adjacent vertices have the same hash, # then we store the pair in the dictionary of conflicts conflicts = {} # 1) List of vertices in the line graph elist = [] for e in self.edge_iterator(labels=labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): elist.append((e[1], e[0]) + e[2:]) else: # Settle the conflict arbitrarily conflicts[e] = e conflicts[(e[1], e[0]) + e[2:]] = e elist.append(e) G.add_vertices(elist) # 2) adjacencies in the line graph for v in self: elist = [] # Add the edge to the list, according to hashes, as previously for e in self.edge_iterator(v, labels=labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): elist.append((e[1], e[0]) + e[2:]) else: elist.append(conflicts[e]) # Alls pairs of elements in elist are edges of the # line graph while elist: x = elist.pop() for y in elist: G.add_edge(x, y) return G
def tournaments_nauty(self, n, min_out_degree=None, max_out_degree=None, strongly_connected=False, debug=False, options=""): r""" Returns all tournaments on `n` vertices using Nauty. INPUT: - ``n`` (integer) -- number of vertices. - ``min_out_degree``, ``max_out_degree`` (integers) -- if set to ``None`` (default), then the min/max out-degree is not constrained. - ``debug`` (boolean) -- if ``True`` the first line of genbg's output to standard error is captured and the first call to the generator's ``next()`` function will return this line as a string. A line leading with ">A" indicates a successful initiation of the program with some information on the arguments, while a line beginning with ">E" indicates an error with the input. - ``options`` (string) -- anything else that should be forwarded as input to Nauty's genbg. See its documentation for more information : `<http://cs.anu.edu.au/~bdm/nauty/>`_. .. NOTE:: To use this method you must first install the Nauty spkg. EXAMPLES:: sage: for g in digraphs.tournaments_nauty(4): # optional - nauty ....: print g.edges(labels = False) # optional - nauty [(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)] [(1, 0), (1, 3), (2, 0), (2, 1), (3, 0), (3, 2)] [(0, 2), (1, 0), (2, 1), (3, 0), (3, 1), (3, 2)] [(0, 2), (0, 3), (1, 0), (2, 1), (3, 1), (3, 2)] sage: tournaments = digraphs.tournaments_nauty sage: [len(list(tournaments(x))) for x in range(1,8)] # optional - nauty [1, 1, 2, 4, 12, 56, 456] sage: [len(list(tournaments(x, strongly_connected = True))) for x in range(1,9)] # optional - nauty [1, 0, 1, 1, 6, 35, 353, 6008] """ import subprocess from sage.misc.package import is_package_installed if not is_package_installed("nauty"): raise TypeError( "The optional nauty spkg does not seem to be installed") nauty_input = options if min_out_degree is None: min_out_degree = 0 if max_out_degree is None: max_out_degree = n - 1 nauty_input += " -d" + str(min_out_degree) nauty_input += " -D" + str(max_out_degree) if strongly_connected: nauty_input += " -c" nauty_input += " " + str(n) + " " sp = subprocess.Popen("nauty-gentourng {0}".format(nauty_input), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) if debug: yield sp.stderr.readline() gen = sp.stdout while True: try: s = gen.next() except StopIteration: raise StopIteration("Exhausted list of graphs from nauty geng") G = DiGraph(n) i = 0 j = 1 for b in s[:-1]: if b == '0': G.add_edge(i, j) else: G.add_edge(j, i) if j == n - 1: i += 1 j = i + 1 else: j += 1 yield G
def RandomPoset(n,p): r""" Generate a random poset on ``n`` vertices according to a probability ``p``. INPUT: - ``n`` - number of vertices, a non-negative integer - ``p`` - a probability, a real number between 0 and 1 (inclusive) OUTPUT: A poset on ``n`` vertices. The construction decides to make an ordered pair of vertices comparable in the poset with probability ``p``, however a pair is not made comparable if it would violate the defining properties of a poset, such as transitivity. So in practice, once the probability exceeds a small number the generated posets may be very similar to a chain. So to create interesting examples, keep the probability small, perhaps on the order of `1/n`. EXAMPLES:: sage: Posets.RandomPoset(17,.15) Finite poset containing 17 elements TESTS:: sage: Posets.RandomPoset('junk', 0.5) Traceback (most recent call last): ... TypeError: number of elements must be an integer, not junk sage: Posets.RandomPoset(-6, 0.5) Traceback (most recent call last): ... ValueError: number of elements must be non-negative, not -6 sage: Posets.RandomPoset(6, 'garbage') Traceback (most recent call last): ... TypeError: probability must be a real number, not garbage sage: Posets.RandomPoset(6, -0.5) Traceback (most recent call last): ... ValueError: probability must be between 0 and 1, not -0.5 """ from sage.misc.prandom import random try: n = Integer(n) except TypeError: raise TypeError("number of elements must be an integer, not {0}".format(n)) if n < 0: raise ValueError("number of elements must be non-negative, not {0}".format(n)) try: p = float(p) except Exception: raise TypeError("probability must be a real number, not {0}".format(p)) if p < 0 or p> 1: raise ValueError("probability must be between 0 and 1, not {0}".format(p)) D = DiGraph(loops=False,multiedges=False) D.add_vertices(range(n)) for i in range(n): for j in range(n): if random() < p: D.add_edge(i,j) if not D.is_directed_acyclic(): D.delete_edge(i,j) return Poset(D,cover_relations=False)
def ImaseItoh(self, n, d): r""" Returns the digraph of Imase and Itoh of order `n` and degree `d`. The digraph of Imase and Itoh has been defined in [II83]_. It has vertex set `V=\{0, 1,..., n-1\}` and there is an arc from vertex `u \in V` to all vertices `v \in V` such that `v \equiv (-u*d-a-1) \mod{n}` with `0 \leq a < d`. When `n = d^{D}`, the digraph of Imase and Itoh is isomorphic to the de Bruijn digraph of degree `d` and diameter `D`. When `n = d^{D-1}(d+1)`, the digraph of Imase and Itoh is isomorphic to the Kautz digraph [Kautz68]_ of degree `d` and diameter `D`. INPUTS: - ``n`` -- is the number of vertices of the digraph - ``d`` -- is the degree of the digraph EXAMPLES:: sage: II = digraphs.ImaseItoh(8, 2) sage: II.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True) (True, {0: '010', 1: '011', 2: '000', 3: '001', 4: '110', 5: '111', 6: '100', 7: '101'}) sage: II = digraphs.ImaseItoh(12, 2) sage: II.is_isomorphic(digraphs.Kautz(2, 3), certify = True) (True, {0: '010', 1: '012', 2: '021', 3: '020', 4: '202', 5: '201', 6: '210', 7: '212', 8: '121', 9: '120', 10: '102', 11: '101'}) TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.ImaseItoh(2, 0) Traceback (most recent call last): ... ValueError: The digraph of Imase and Itoh is defined for degree at least one. An exception is raised when the order of the graph is less than two:: sage: G = digraphs.ImaseItoh(1, 2) Traceback (most recent call last): ... ValueError: The digraph of Imase and Itoh is defined for at least two vertices. REFERENCE: .. [II83] M. Imase and M. Itoh. A design for directed graphs with minimum diameter, *IEEE Trans. Comput.*, vol. C-32, pp. 782-784, 1983. """ if n < 2: raise ValueError( "The digraph of Imase and Itoh is defined for at least two vertices." ) if d < 1: raise ValueError( "The digraph of Imase and Itoh is defined for degree at least one." ) II = DiGraph(loops=True) II.allow_multiple_edges(True) for u in xrange(n): for a in xrange(-u * d - d, -u * d): II.add_edge(u, a % n) II.name("Imase and Itoh digraph (n=%s, d=%s)" % (n, d)) return II
def spanning_tree(self): r""" Return a spanning tree OUTPUT: - spanning tree as a DiGraph - remaining edges as 2-tuples ``(i,e[i])`` EXAMPLES:: sage: from surface_dynamics import * sage: R = RibbonGraph('(1,2,3)','(1,2)(3,4)') sage: R Ribbon graph with 2 vertices, 2 edges and 2 faces sage: T,o = R.spanning_tree() sage: T Digraph on 2 vertices sage: T.edges() [(0, 1, (3, 4))] sage: o [(1, 2)] sage: R = RibbonGraph('(1,2,3)(4,5,6)','(1,2)(3,4)(5,6)') sage: R Ribbon graph with 2 vertices, 3 edges and 3 faces sage: T,o = R.spanning_tree() sage: T Digraph on 2 vertices sage: T.edges() [(0, 1, (3, 4))] sage: o [(1, 2), (5, 6)] sage: e = '(1,3)(5,7)(2,4)(6,8)' sage: f = '(1,2,3,4,5,6,7,8)' sage: R = RibbonGraph(edges=e, faces=f) sage: T,o = R.spanning_tree() sage: T Digraph on 1 vertex sage: o [[1, 3], [2, 4], [5, 7], [6, 8]] """ from sage.graphs.digraph import DiGraph d = self.darts() v = self.vertices() e = self.edge_perm() if self.num_darts() == 0: return DiGraph(),[] if self.num_vertices() == 1: return DiGraph({0:[]}),self.edges() T = DiGraph() v0 = 0 for root in v[0]: v1 = self.dart_to_vertex(e[root]) if v1 != 0: break T.add_edge(v0,v1,(root,e[root])) o = [] seen = set([v0,v1]) # seen vertices waiting = [e[root],root] # waiting darts cc = [] while waiting: ii = waiting.pop() # this is a dart v0 = self.dart_to_vertex(ii) seen.add(v0) for j in self.vertex_orbit(ii)[1:]: v1 = self.dart_to_vertex(e[j]) if v1 in seen: if j < e[j]: o.append((j,e[j])) else: T.add_edge(v0,v1,(j,e[j])) waiting.append(e[j]) seen.add(v1) return T, sorted(o)
def tournaments_nauty(self, n, min_out_degree = None, max_out_degree = None, strongly_connected = False, debug=False, options=""): r""" Returns all tournaments on `n` vertices using Nauty. INPUT: - ``n`` (integer) -- number of vertices. - ``min_out_degree``, ``max_out_degree`` (integers) -- if set to ``None`` (default), then the min/max out-degree is not constrained. - ``debug`` (boolean) -- if ``True`` the first line of genbg's output to standard error is captured and the first call to the generator's ``next()`` function will return this line as a string. A line leading with ">A" indicates a successful initiation of the program with some information on the arguments, while a line beginning with ">E" indicates an error with the input. - ``options`` (string) -- anything else that should be forwarded as input to Nauty's genbg. See its documentation for more information : `<http://cs.anu.edu.au/~bdm/nauty/>`_. .. NOTE:: To use this method you must first install the Nauty spkg. EXAMPLES:: sage: for g in digraphs.tournaments_nauty(4): # optional - nauty ....: print g.edges(labels = False) # optional - nauty [(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)] [(1, 0), (1, 3), (2, 0), (2, 1), (3, 0), (3, 2)] [(0, 2), (1, 0), (2, 1), (3, 0), (3, 1), (3, 2)] [(0, 2), (0, 3), (1, 0), (2, 1), (3, 1), (3, 2)] sage: tournaments = digraphs.tournaments_nauty sage: [len(list(tournaments(x))) for x in range(1,8)] # optional - nauty [1, 1, 2, 4, 12, 56, 456] sage: [len(list(tournaments(x, strongly_connected = True))) for x in range(1,9)] # optional - nauty [1, 0, 1, 1, 6, 35, 353, 6008] """ import subprocess from sage.misc.package import is_package_installed if not is_package_installed("nauty"): raise TypeError("The optional nauty spkg does not seem to be installed") nauty_input = options if min_out_degree is None: min_out_degree = 0 if max_out_degree is None: max_out_degree = n-1 nauty_input += " -d"+str(min_out_degree) nauty_input += " -D"+str(max_out_degree) if strongly_connected: nauty_input += " -c" nauty_input += " "+str(n) +" " sp = subprocess.Popen("nauty-gentourng {0}".format(nauty_input), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) if debug: yield sp.stderr.readline() gen = sp.stdout while True: try: s = gen.next() except StopIteration: raise StopIteration("Exhausted list of graphs from nauty geng") G = DiGraph(n) i = 0 j = 1 for b in s[:-1]: if b == '0': G.add_edge(i,j) else: G.add_edge(j,i) if j == n-1: i += 1 j = i+1 else: j += 1 yield G
def is_partial_cube(G, certificate=False): r""" Test whether the given graph is a partial cube. A partial cube is a graph that can be isometrically embedded into a hypercube, i.e., its vertices can be labelled with (0,1)-vectors of some fixed length such that the distance between any two vertices in the graph equals the Hamming distance of their labels. Originally written by D. Eppstein for the PADS library (http://www.ics.uci.edu/~eppstein/PADS/), see also [Epp2008]_. The algorithm runs in `O(n^2)` time, where `n` is the number of vertices. See the documentation of :mod:`~sage.graphs.partial_cube` for an overview of the algorithm. INPUT: - ``certificate`` -- boolean (default: ``False``); this function returns ``True`` or ``False`` according to the graph, when ``certificate = False``. When ``certificate = True`` and the graph is a partial cube, the function returns ``(True, mapping)``, where ``mapping`` is an isometric mapping of the vertices of the graph to the vertices of a hypercube ((0, 1)-strings of a fixed length). When ``certificate = True`` and the graph is not a partial cube, ``(False, None)`` is returned. EXAMPLES: The Petersen graph is not a partial cube:: sage: g = graphs.PetersenGraph() sage: g.is_partial_cube() False All prisms are partial cubes:: sage: g = graphs.CycleGraph(10).cartesian_product(graphs.CompleteGraph(2)) sage: g.is_partial_cube() True TESTS: The returned mapping is an isometric embedding into a hypercube:: sage: g = graphs.DesarguesGraph() sage: _, m = g.is_partial_cube(certificate=True) sage: m # random {0: '00000', 1: '00001', 2: '00011', 3: '01011', 4: '11011', 5: '11111', 6: '11110', 7: '11100', 8: '10100', 9: '00100', 10: '01000', 11: '10001', 12: '00111', 13: '01010', 14: '11001', 15: '10111', 16: '01110', 17: '11000', 18: '10101', 19: '00110'} sage: all(all(g.distance(u, v) == len([i for i in range(len(m[u])) if m[u][i] != m[v][i]]) for v in m) for u in m) True A graph without vertices is trivially a partial cube:: sage: Graph().is_partial_cube(certificate=True) (True, {}) """ G._scream_if_not_simple() if not G.order(): if certificate: return (True, {}) else: return True if certificate: fail = (False, None) else: fail = False if not G.is_connected(): return fail n = G.order() # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. if 1 << (2 * G.size() // n) > n: return fail # Check for bipartiteness. # This ensures also that each contraction will be bipartite. if not G.is_bipartite(): return fail # Set up data structures for algorithm: # - contracted: contracted graph at current stage of algorithm # - unionfind: union find data structure representing known edge equivalences # - available: limit on number of remaining available labels from sage.graphs.digraph import DiGraph from sage.graphs.graph import Graph from sage.sets.disjoint_set import DisjointSet contracted = DiGraph({v: {w: (v, w) for w in G[v]} for v in G}) unionfind = DisjointSet(contracted.edges(labels=False)) available = n - 1 # Main contraction loop in place of the original algorithm's recursion while contracted.order() > 1: # Find max degree vertex in contracted, and update label limit deg, root = max([(contracted.out_degree(v), v) for v in contracted], key=lambda x: x[0]) if deg > available: return fail available -= deg # Set up bitvectors on vertices bitvec = {v: 0 for v in contracted} neighbors = {} for i, neighbor in enumerate(contracted[root]): bitvec[neighbor] = 1 << i neighbors[1 << i] = neighbor # Breadth first search to propagate bitvectors to the rest of the graph for level in breadth_first_level_search(contracted, root): for v in level: for w in level[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = Graph([contracted.vertices(), []]) for v, w in contracted.edge_iterator(labels=False): diff = bitvec[v] ^ bitvec[w] if not diff or not bitvec[w] & ~bitvec[v]: continue # zero edge or wrong direction if diff not in neighbors: return fail neighbor = neighbors[diff] unionfind.union(contracted.edge_label(v, w), contracted.edge_label(root, neighbor)) unionfind.union(contracted.edge_label(w, v), contracted.edge_label(neighbor, root)) labeled.add_edge(v, w) # Map vertices to components of labeled-edge graph component = {} for i, SCC in enumerate(labeled.connected_components()): for v in SCC: component[v] = i # generate new compressed subgraph newgraph = DiGraph() for v, w, t in contracted.edge_iterator(): if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: return fail if newgraph.has_edge(vi, wi): unionfind.union(newgraph.edge_label(vi, wi), t) else: newgraph.add_edge(vi, wi, t) contracted = newgraph # Make a digraph with edges labeled by the equivalence classes in unionfind g = DiGraph({v: {w: unionfind.find((v, w)) for w in G[v]} for v in G}) # Associates to a vertex the token that acts on it, an check that # no two edges on a single vertex have the same label action = {} for v in g: action[v] = set(t for _, _, t in g.edge_iterator(v)) if len(action[v]) != g.out_degree(v): return fail # Associate every token to its reverse reverse = {} for v, w, t in g.edge_iterator(): rt = g.edge_label(w, v) reverse[t] = rt reverse[rt] = t current = initialState = next(g.vertex_iterator()) # A token T is said to be 'active' for a vertex u if it takes u # one step closer to the source in terms of distance. The 'source' # is initially 'initialState'. See the module's documentation for # more explanations. # Find list of tokens that lead to the initial state activeTokens = set() for level in breadth_first_level_search(g, initialState): for v in level: for w in level[v]: activeTokens.add(g.edge_label(w, v)) for t in activeTokens: if reverse[t] in activeTokens: return fail activeTokens = list(activeTokens) # Rest of data structure: point from states to list and list to states state_to_active_token = {v: -1 for v in g} token_to_states = [[] for i in activeTokens ] # (i.e. vertices on which each token acts) def scan(v): """Find the next token that is effective for v.""" a = next( i for i in range(state_to_active_token[v] + 1, len(activeTokens)) if activeTokens[i] is not None and activeTokens[i] in action[v]) state_to_active_token[v] = a token_to_states[a].append(v) # Initialize isometric embedding into a hypercube if certificate: dim = 0 tokmap = {} for t in reverse: if t not in tokmap: tokmap[t] = tokmap[reverse[t]] = 1 << dim dim += 1 embed = {initialState: 0} # Set initial active states for v in g: if v != current: try: scan(v) except StopIteration: return fail # Traverse the graph, maintaining active tokens for prev, current, fwd in depth_first_traversal(g, initialState): if not fwd: prev, current = current, prev elif certificate: embed[current] = embed[prev] ^ tokmap[g.edge_label(prev, current)] # Add token to end of list, point to it from old state activeTokens.append(g.edge_label(prev, current)) state_to_active_token[prev] = len(activeTokens) - 1 token_to_states.append([prev]) # Inactivate reverse token, find new token for its states # # (the 'active' token of 'current' is necessarily the label of # (current, previous)) activeTokens[state_to_active_token[current]] = None for v in token_to_states[state_to_active_token[current]]: if v != current: try: scan(v) except StopIteration: return fail # All checks passed, return the result if certificate: format = "{0:0%db}" % dim return (True, {v: format.format(l) for v, l in embed.items()}) else: return True
def Hasse_diagram_from_incidences(atom_to_coatoms, coatom_to_atoms, face_constructor=None, required_atoms=None, key = None, **kwds): r""" Compute the Hasse diagram of an atomic and coatomic lattice. INPUT: - ``atom_to_coatoms`` -- list, ``atom_to_coatom[i]`` should list all coatoms over the ``i``-th atom; - ``coatom_to_atoms`` -- list, ``coatom_to_atom[i]`` should list all atoms under the ``i``-th coatom; - ``face_constructor`` -- function or class taking as the first two arguments sorted :class:`tuple` of integers and any keyword arguments. It will be called to construct a face over atoms passed as the first argument and under coatoms passed as the second argument. Default implementation will just return these two tuples as a tuple; - ``required_atoms`` -- list of atoms (default:None). Each non-empty "face" requires at least on of the specified atoms present. Used to ensure that each face has a vertex. - ``key`` -- any hashable value (default: None). It is passed down to :class:`~sage.combinat.posets.posets.FinitePoset`. - all other keyword arguments will be passed to ``face_constructor`` on each call. OUTPUT: - :class:`finite poset <sage.combinat.posets.posets.FinitePoset>` with elements constructed by ``face_constructor``. .. NOTE:: In addition to the specified partial order, finite posets in Sage have internal total linear order of elements which extends the partial one. This function will try to make this internal order to start with the bottom and atoms in the order corresponding to ``atom_to_coatoms`` and to finish with coatoms in the order corresponding to ``coatom_to_atoms`` and the top. This may not be possible if atoms and coatoms are the same, in which case the preference is given to the first list. ALGORITHM: The detailed description of the used algorithm is given in [KP2002]_. The code of this function follows the pseudo-code description in the section 2.5 of the paper, although it is mostly based on frozen sets instead of sorted lists - this makes the implementation easier and should not cost a big performance penalty. (If one wants to make this function faster, it should be probably written in Cython.) While the title of the paper mentions only polytopes, the algorithm (and the implementation provided here) is applicable to any atomic and coatomic lattice if both incidences are given, see Section 3.4. In particular, this function can be used for strictly convex cones and complete fans. REFERENCES: [KP2002]_ AUTHORS: - Andrey Novoseltsev (2010-05-13) with thanks to Marshall Hampton for the reference. EXAMPLES: Let's construct the Hasse diagram of a lattice of subsets of {0, 1, 2}. Our atoms are {0}, {1}, and {2}, while our coatoms are {0,1}, {0,2}, and {1,2}. Then incidences are :: sage: atom_to_coatoms = [(0,1), (0,2), (1,2)] sage: coatom_to_atoms = [(0,1), (0,2), (1,2)] and we can compute the Hasse diagram as :: sage: L = sage.geometry.cone.Hasse_diagram_from_incidences( ... atom_to_coatoms, coatom_to_atoms) sage: L Finite poset containing 8 elements with distinguished linear extension sage: for level in L.level_sets(): print(level) [((), (0, 1, 2))] [((0,), (0, 1)), ((1,), (0, 2)), ((2,), (1, 2))] [((0, 1), (0,)), ((0, 2), (1,)), ((1, 2), (2,))] [((0, 1, 2), ())] For more involved examples see the *source code* of :meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.face_lattice` and :meth:`sage.geometry.fan.RationalPolyhedralFan._compute_cone_lattice`. """ from sage.graphs.digraph import DiGraph from sage.combinat.posets.posets import FinitePoset def default_face_constructor(atoms, coatoms, **kwds): return (atoms, coatoms) if face_constructor is None: face_constructor = default_face_constructor atom_to_coatoms = [frozenset(atc) for atc in atom_to_coatoms] A = frozenset(range(len(atom_to_coatoms))) # All atoms coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms] C = frozenset(range(len(coatom_to_atoms))) # All coatoms # Comments with numbers correspond to steps in Section 2.5 of the article L = DiGraph(1) # 3: initialize L faces = dict() atoms = frozenset() coatoms = C faces[atoms, coatoms] = 0 next_index = 1 Q = [(atoms, coatoms)] # 4: initialize Q with the empty face while Q: # 5 q_atoms, q_coatoms = Q.pop() # 6: remove some q from Q q = faces[q_atoms, q_coatoms] # 7: compute H = {closure(q+atom) : atom not in atoms of q} H = dict() candidates = set(A.difference(q_atoms)) for atom in candidates: coatoms = q_coatoms.intersection(atom_to_coatoms[atom]) atoms = A for coatom in coatoms: atoms = atoms.intersection(coatom_to_atoms[coatom]) H[atom] = (atoms, coatoms) # 8: compute the set G of minimal sets in H minimals = set([]) while candidates: candidate = candidates.pop() atoms = H[candidate][0] if atoms.isdisjoint(candidates) and atoms.isdisjoint(minimals): minimals.add(candidate) # Now G == {H[atom] : atom in minimals} for atom in minimals: # 9: for g in G: g_atoms, g_coatoms = H[atom] if not required_atoms is None: if g_atoms.isdisjoint(required_atoms): continue if (g_atoms, g_coatoms) in faces: g = faces[g_atoms, g_coatoms] else: # 11: if g was newly created g = next_index faces[g_atoms, g_coatoms] = g next_index += 1 Q.append((g_atoms, g_coatoms)) # 12 L.add_edge(q, g) # 14 # End of algorithm, now construct a FinitePoset. # In principle, it is recommended to use Poset or in this case perhaps # even LatticePoset, but it seems to take several times more time # than the above computation, makes unnecessary copies, and crashes. # So for now we will mimic the relevant code from Poset. # Enumeration of graph vertices must be a linear extension of the poset new_order = L.topological_sort() # Make sure that coatoms are in the end in proper order tail = [faces[atoms, frozenset([coatom])] for coatom, atoms in enumerate(coatom_to_atoms)] tail.append(faces[A, frozenset()]) new_order = [n for n in new_order if n not in tail] + tail # Make sure that atoms are in the beginning in proper order head = [0] # We know that the empty face has index 0 head.extend(faces[frozenset([atom]), coatoms] for atom, coatoms in enumerate(atom_to_coatoms) if required_atoms is None or atom in required_atoms) new_order = head + [n for n in new_order if n not in head] # "Invert" this list to a dictionary labels = dict() for new, old in enumerate(new_order): labels[old] = new L.relabel(labels) # Construct the actual poset elements elements = [None] * next_index for face, index in faces.items(): atoms, coatoms = face elements[labels[index]] = face_constructor( tuple(sorted(atoms)), tuple(sorted(coatoms)), **kwds) D = {i:f for i,f in enumerate(elements)} L.relabel(D) return FinitePoset(L, elements, key = key)
def line_graph(self, labels=True): """ Returns the line graph of the (di)graph. INPUT: - ``labels`` (boolean) -- whether edge labels should be taken in consideration. If ``labels=True``, the vertices of the line graph will be triples ``(u,v,label)``, and pairs of vertices otherwise. This is set to ``True`` by default. The line graph of an undirected graph G is an undirected graph H such that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G. In other words, an edge in H represents a path of length 2 in G. The line graph of a directed graph G is a directed graph H such that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G and the terminal vertex of e is the initial vertex of f. In other words, an edge in H represents a (directed) path of length 2 in G. .. NOTE:: As a :class:`Graph` object only accepts hashable objects as vertices (and as the vertices of the line graph are the edges of the graph), this code will fail if edge labels are not hashable. You can also set the argument ``labels=False`` to ignore labels. .. SEEALSO:: - The :mod:`line_graph <sage.graphs.line_graph>` module. - :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs` -- the forbidden subgraphs of a line graph. - :meth:`~Graph.is_line_graph` -- tests whether a graph is a line graph. EXAMPLES:: sage: g = graphs.CompleteGraph(4) sage: h = g.line_graph() sage: h.vertices() [(0, 1, None), (0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None), (2, 3, None)] sage: h.am() [0 1 1 1 1 0] [1 0 1 1 0 1] [1 1 0 0 1 1] [1 1 0 0 1 1] [1 0 1 1 0 1] [0 1 1 1 1 0] sage: h2 = g.line_graph(labels=False) sage: h2.vertices() [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] sage: h2.am() == h.am() True sage: g = DiGraph([[1..4],lambda i,j: i<j]) sage: h = g.line_graph() sage: h.vertices() [(1, 2, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None), (3, 4, None)] sage: h.edges() [((1, 2, None), (2, 3, None), None), ((1, 2, None), (2, 4, None), None), ((1, 3, None), (3, 4, None), None), ((2, 3, None), (3, 4, None), None)] Tests: :trac:`13787`:: sage: g = graphs.KneserGraph(7,1) sage: C = graphs.CompleteGraph(7) sage: C.is_isomorphic(g) True sage: C.line_graph().is_isomorphic(g.line_graph()) True """ self._scream_if_not_simple() if self._directed: from sage.graphs.digraph import DiGraph G=DiGraph() G.add_vertices(self.edges(labels=labels)) for v in self: # Connect appropriate incident edges of the vertex v G.add_edges([(e,f) for e in self.incoming_edge_iterator(v, labels=labels) \ for f in self.outgoing_edge_iterator(v, labels=labels)]) return G else: from sage.graphs.all import Graph G=Graph() # We must sort the edges' endpoints so that (1,2,None) is seen as # the same edge as (2,1,None). # # We do so by comparing hashes, just in case all the natural order # (<) on vertices would not be a total order (for instance when # vertices are sets). If two adjacent vertices have the same hash, # then we store the pair in the dictionary of conflicts conflicts = {} # 1) List of vertices in the line graph elist = [] for e in self.edge_iterator(labels = labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): elist.append((e[1],e[0])+e[2:]) else: # Settle the conflict arbitrarily conflicts[e] = e conflicts[(e[1],e[0])+e[2:]] = e elist.append(e) G.add_vertices(elist) # 2) adjacencies in the line graph for v in self: elist = [] # Add the edge to the list, according to hashes, as previously for e in self.edge_iterator(v, labels=labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): elist.append((e[1],e[0])+e[2:]) else: elist.append(conflicts[e]) # Alls pairs of elements in elist are edges of the # line graph while elist: x = elist.pop() for y in elist: G.add_edge(x,y) return G
def ImaseItoh(self, n, d): r""" Returns the digraph of Imase and Itoh of order `n` and degree `d`. The digraph of Imase and Itoh has been defined in [II83]_. It has vertex set `V=\{0, 1,..., n-1\}` and there is an arc from vertex `u \in V` to all vertices `v \in V` such that `v \equiv (-u*d-a-1) \mod{n}` with `0 \leq a < d`. When `n = d^{D}`, the digraph of Imase and Itoh is isomorphic to the de Bruijn digraph of degree `d` and diameter `D`. When `n = d^{D-1}(d+1)`, the digraph of Imase and Itoh is isomorphic to the Kautz digraph [Kautz68]_ of degree `d` and diameter `D`. INPUTS: - ``n`` -- is the number of vertices of the digraph - ``d`` -- is the degree of the digraph EXAMPLES:: sage: II = digraphs.ImaseItoh(8, 2) sage: II.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True) (True, {0: '010', 1: '011', 2: '000', 3: '001', 4: '110', 5: '111', 6: '100', 7: '101'}) sage: II = digraphs.ImaseItoh(12, 2) sage: II.is_isomorphic(digraphs.Kautz(2, 3), certify = True) (True, {0: '010', 1: '012', 2: '021', 3: '020', 4: '202', 5: '201', 6: '210', 7: '212', 8: '121', 9: '120', 10: '102', 11: '101'}) TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.ImaseItoh(2, 0) Traceback (most recent call last): ... ValueError: The digraph of Imase and Itoh is defined for degree at least one. An exception is raised when the order of the graph is less than two:: sage: G = digraphs.ImaseItoh(1, 2) Traceback (most recent call last): ... ValueError: The digraph of Imase and Itoh is defined for at least two vertices. REFERENCE: .. [II83] M. Imase and M. Itoh. A design for directed graphs with minimum diameter, *IEEE Trans. Comput.*, vol. C-32, pp. 782-784, 1983. """ if n < 2: raise ValueError("The digraph of Imase and Itoh is defined for at least two vertices.") if d < 1: raise ValueError("The digraph of Imase and Itoh is defined for degree at least one.") II = DiGraph(loops = True) II.allow_multiple_edges(True) for u in xrange(n): for a in xrange(-u*d-d, -u*d): II.add_edge(u, a % n) II.name( "Imase and Itoh digraph (n=%s, d=%s)"%(n,d) ) return II
def DeBruijn(self, k, n, vertices = 'strings'): r""" Returns the De Bruijn digraph with parameters `k,n`. The De Bruijn digraph with parameters `k,n` is built upon a set of vertices equal to the set of words of length `n` from a dictionary of `k` letters. In this digraph, there is an arc `w_1w_2` if `w_2` can be obtained from `w_1` by removing the leftmost letter and adding a new letter at its right end. For more information, see the :wikipedia:`Wikipedia article on De Bruijn graph <De_Bruijn_graph>`. INPUT: - ``k`` -- Two possibilities for this parameter : - An integer equal to the cardinality of the alphabet to use, that is the degree of the digraph to be produced. - An iterable object to be used as the set of letters. The degree of the resulting digraph is the cardinality of the set of letters. - ``n`` -- An integer equal to the length of words in the De Bruijn digraph when ``vertices == 'strings'``, and also to the diameter of the digraph. - ``vertices`` -- 'strings' (default) or 'integers', specifying whether the vertices are words build upon an alphabet or integers. EXAMPLES:: sage: db=digraphs.DeBruijn(2,2); db De Bruijn digraph (k=2, n=2): Looped digraph on 4 vertices sage: db.order() 4 sage: db.size() 8 TESTS:: sage: digraphs.DeBruijn(5,0) De Bruijn digraph (k=5, n=0): Looped multi-digraph on 1 vertex sage: digraphs.DeBruijn(0,0) De Bruijn digraph (k=0, n=0): Looped multi-digraph on 0 vertices """ from sage.combinat.words.words import Words from sage.rings.integer import Integer W = Words(range(k) if isinstance(k, Integer) else k, n) A = Words(range(k) if isinstance(k, Integer) else k, 1) g = DiGraph(loops=True) if vertices == 'strings': if n == 0 : g.allow_multiple_edges(True) v = W[0] for a in A: g.add_edge(v.string_rep(), v.string_rep(), a.string_rep()) else: for w in W: ww = w[1:] for a in A: g.add_edge(w.string_rep(), (ww*a).string_rep(), a.string_rep()) else: d = W.size_of_alphabet() g = digraphs.GeneralizedDeBruijn(d**n, d) g.name( "De Bruijn digraph (k=%s, n=%s)"%(k,n) ) return g
def strong_orientations_iterator(G): r""" Returns an iterator over all strong orientations of a graph `G`. A strong orientation of a graph is an orientation of its edges such that the obtained digraph is strongly connected (i.e. there exist a directed path between each pair of vertices). ALGORITHM: It is an adaptation of the algorithm published in [CGMRV16]_. It runs in `O(mn)` amortized time, where `m` is the number of edges and `n` is the number of vertices. The amortized time can be improved to `O(m)` with a more involved method. In this function, first the graph is preprocessed and a spanning tree is generated. Then every orientation of the non-tree edges of the graph can be extended to at least one new strong orientation by orienting properly the edges of the spanning tree (this property is proved in [CGMRV16]_). Therefore, this function generates all partial orientations of the non-tree edges and then launches a helper function corresponding to the generation algorithm described in [CGMRV16]_. In order to avoid trivial symetries, the orientation of an arbitrary edge is fixed before the start of the enumeration process. INPUT: - ``G`` -- an undirected graph. OUTPUT: - an iterator which will produce all strong orientations of this graph. .. NOTE:: Works only for simple graphs (no multiple edges). In order to avoid symetries an orientation of an arbitrary edge is fixed. EXAMPLES: A cycle has one possible (non-symmetric) strong orientation:: sage: g = graphs.CycleGraph(4) sage: it = g.strong_orientations_iterator() sage: len(list(it)) 1 A tree cannot be strongly oriented:: sage: g = graphs.RandomTree(100) sage: len(list(g.strong_orientations_iterator())) 0 Neither can be a disconnected graph:: sage: g = graphs.CompleteGraph(6) sage: g.add_vertex(7) sage: len(list(g.strong_orientations_iterator())) 0 TESTS: sage: g = graphs.CompleteGraph(2) sage: len(list(g.strong_orientations_iterator())) 0 sage: g = graphs.CubeGraph(3) sage: b = True sage: for orientedGraph in g.strong_orientations_iterator(): ....: if not orientedGraph.is_strongly_connected(): ....: b = False sage: b True The total number of strong orientations of a graph can be counted using the Tutte polynomial evaluated at points (0,2):: sage: g = graphs.PetersenGraph() sage: nr1 = len(list(g.strong_orientations_iterator())) sage: nr2 = g.tutte_polynomial()(0,2) sage: nr1 == nr2/2 # The Tutte polynomial counts also the symmetrical orientations True """ # if the graph has a bridge or is disconnected, # then it cannot be strongly oriented if G.order() < 3 or not G.is_biconnected(): return V = G.vertices() Dg = DiGraph([G.vertices(), G.edges()], pos=G.get_pos()) # compute an arbitrary spanning tree of the undirected graph te = kruskal(G) treeEdges = [(u,v) for u,v,_ in te] A = [edge for edge in G.edges(labels=False) if edge not in treeEdges] # initialization of the first binary word 00...0 # corresponding to the current orientation of the non-tree edges existingAedges = [0]*len(A) # Make the edges of the spanning tree doubly oriented for e in treeEdges: if Dg.has_edge(e): Dg.add_edge(e[1], e[0]) else: Dg.add_edge(e) # Generate all orientations for non-tree edges (using Gray code) # Each of these orientations can be extended to a strong orientation # of G by orienting properly the tree-edges previousWord = 0 i = 0 # the orientation of one edge is fixed so we consider one edge less nr = 2**(len(A)-1) while i < nr: word = (i >> 1) ^ i bitChanged = word ^ previousWord bit = 0 while bitChanged > 1: bitChanged >>= 1 bit += 1 previousWord = word if existingAedges[bit] == 0: Dg.reverse_edge(A[bit]) existingAedges[bit] = 1 else: Dg.reverse_edge(A[bit][1], A[bit][0]) existingAedges[bit] = 0 # launch the algorithm for enumeration of the solutions for sol in _strong_orientations_of_a_mixed_graph(Dg, V, treeEdges): yield sol i = i + 1
def Kautz(self, k, D, vertices = 'strings'): r""" Returns the Kautz digraph of degree `d` and diameter `D`. The Kautz digraph has been defined in [Kautz68]_. The Kautz digraph of degree `d` and diameter `D` has `d^{D-1}(d+1)` vertices. This digraph is build upon a set of vertices equal to the set of words of length `D` from an alphabet of `d+1` letters such that consecutive letters are differents. There is an arc from vertex `u` to vertex `v` if `v` can be obtained from `u` by removing the leftmost letter and adding a new letter, distinct from the rightmost letter of `u`, at the right end. The Kautz digraph of degree `d` and diameter `D` is isomorphic to the digraph of Imase and Itoh [II83]_ of degree `d` and order `d^{D-1}(d+1)`. See also the :wikipedia:`Wikipedia article on Kautz Graphs <Kautz_graph>`. INPUTS: - ``k`` -- Two possibilities for this parameter : - An integer equal to the degree of the digraph to be produced, that is the cardinality minus one of the alphabet to use. - An iterable object to be used as the set of letters. The degree of the resulting digraph is the cardinality of the set of letters minus one. - ``D`` -- An integer equal to the diameter of the digraph, and also to the length of a vertex label when ``vertices == 'strings'``. - ``vertices`` -- 'strings' (default) or 'integers', specifying whether the vertices are words build upon an alphabet or integers. EXAMPLES:: sage: K = digraphs.Kautz(2, 3) sage: K.is_isomorphic(digraphs.ImaseItoh(12, 2), certify = True) (True, {'010': 0, '012': 1, '020': 3, '021': 2, '101': 11, '102': 10, '120': 9, '121': 8, '201': 5, '202': 4, '210': 6, '212': 7}) sage: K = digraphs.Kautz([1,'a','B'], 2) sage: K.edges() [('1B', 'B1', '1'), ('1B', 'Ba', 'a'), ('1a', 'a1', '1'), ('1a', 'aB', 'B'), ('B1', '1B', 'B'), ('B1', '1a', 'a'), ('Ba', 'a1', '1'), ('Ba', 'aB', 'B'), ('a1', '1B', 'B'), ('a1', '1a', 'a'), ('aB', 'B1', '1'), ('aB', 'Ba', 'a')] sage: K = digraphs.Kautz([1,'aA','BB'], 2) sage: K.edges() [('1,BB', 'BB,1', '1'), ('1,BB', 'BB,aA', 'aA'), ('1,aA', 'aA,1', '1'), ('1,aA', 'aA,BB', 'BB'), ('BB,1', '1,BB', 'BB'), ('BB,1', '1,aA', 'aA'), ('BB,aA', 'aA,1', '1'), ('BB,aA', 'aA,BB', 'BB'), ('aA,1', '1,BB', 'BB'), ('aA,1', '1,aA', 'aA'), ('aA,BB', 'BB,1', '1'), ('aA,BB', 'BB,aA', 'aA')] TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.Kautz(0, 2) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for degree at least one. sage: G = digraphs.Kautz(['a'], 2) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for degree at least one. An exception is raised when the diameter of the graph is less than one:: sage: G = digraphs.Kautz(2, 0) Traceback (most recent call last): ... ValueError: Kautz digraphs are defined for diameter at least one. REFERENCE: .. [Kautz68] W. H. Kautz. Bounds on directed (d, k) graphs. Theory of cellular logic networks and machines, AFCRL-68-0668, SRI Project 7258, Final Rep., pp. 20-28, 1968. """ if D < 1: raise ValueError("Kautz digraphs are defined for diameter at least one.") from sage.combinat.words.words import Words from sage.rings.integer import Integer my_alphabet = Words([str(i) for i in range(k+1)] if isinstance(k, Integer) else k, 1) if my_alphabet.size_of_alphabet() < 2: raise ValueError("Kautz digraphs are defined for degree at least one.") if vertices == 'strings': # We start building the set of vertices V = [i for i in my_alphabet] for i in range(D-1): VV = [] for w in V: VV += [w*a for a in my_alphabet if not w.has_suffix(a) ] V = VV # We now build the set of arcs G = DiGraph() for u in V: for a in my_alphabet: if not u.has_suffix(a): G.add_edge(u.string_rep(), (u[1:]*a).string_rep(), a.string_rep()) else: d = my_alphabet.size_of_alphabet()-1 G = digraphs.ImaseItoh( (d+1)*(d**(D-1)), d) G.name( "Kautz digraph (k=%s, D=%s)"%(k,D) ) return G
class ParentBigOh(Parent,UniqueRepresentation): def __init__(self,ambiant): try: self._uniformizer = ambiant.uniformizer_name() except NotImplementedError: raise TypeError("Impossible to determine the name of the uniformizer") self._ambiant_space = ambiant self._models = DiGraph(loops=False) self._default_model = None Parent.__init__(self,ambiant.base_ring()) if self.base_ring() is None: self._base_precision = None else: if self.base_ring() == ambiant: self._base_precision = self else: self._base_precision = ParentBigOh(self.base_ring()) def __hash__(self): return id(self) def base_precision(self): return self._base_precision def precision(self): return self._precision def default_model(self): if self._default_mode is None: self.set_default_model() return self._default_model def set_default_model(self,model=None): if model is None: self._default_model = self._models.topological_sort()[-1] else: if self._models.has_vertex(model): self._default_model = model else: raise ValueError def add_model(self,model): from bigoh import BigOh if not isinstance(model,list): model = [model] for m in model: if not issubclass(m,BigOh): raise TypeError("A precision model must derive from BigOh but '%s' is not"%m) self._models.add_vertex(m) def delete_model(self,model): if isinstance(model,list): model = [model] for m in model: if self._models.has_vertex(m): self._models.delete_vertex(m) def update_model(self,old,new): from bigoh import BigOh if self._models.has_vertex(old): if not issubclass(new,BigOh): raise TypeError("A precision model must derive from BigOh but '%s' is not"%new) self._models.relabel({old:new}) else: raise ValueError("Model '%m' does not exist"%old) def add_modelconversion(self,domain,codomain,constructor=None,safemode=False): if not self._models.has_vertex(domain): if safemode: return raise ValueError("Model '%s' does not exist"%domain) if not self._models.has_vertex(codomain): if safemode: return raise ValueError("Model '%s' does not exist"%codomain) path = self._models.shortest_path(codomain,domain) if len(path) > 0: raise ValueError("Adding this conversion creates a cycle") self._models.add_edge(domain,codomain,constructor) def delete_modelconversion(self,domain,codomain): if not self._models.has_vertex(domain): raise ValueError("Model '%s' does not exist"%domain) if not self._models.has_vertex(codomain): raise ValueError("Model '%s' does not exist"%codomain) if not self._models_has_edge(domain,codomain): raise ValueError("No conversion from %s to %s"%(domain,codomain)) self._modelfs.delete_edge(domain,codomain) def uniformizer_name(self): return self._uniformizer def ambiant_space(self): return self._ambiant_space # ?!? def __call__(self,*args,**kwargs): return self._element_constructor_(*args,**kwargs) def _element_constructor_(self, *args, **kwargs): if kwargs.has_key('model'): del kwargs['model'] return kwargs['model'](self, *args, **kwargs) if len(args) > 0: from precision.bigoh import BigOh, ExactBigOh arg = args[0] if isinstance(arg,BigOh) and arg.is_exact(): return ExactBigOh(self) if self._models.has_vertex(arg.__class__) and arg.parent() is self: return arg if self._default_model is not None: try: return self._default_model(self,*args,**kwargs) except (AttributeError,ValueError,TypeError): pass models = self._models.topological_sort() models.reverse() for m in models: try: return m(self,*args,**kwargs) except (AttributeError,ValueError,TypeError,PrecisionError): pass raise PrecisionError("unable to create a BigOh object") def __repr__(self): return "Parent for BigOh in %s" % self._ambiant_space def models(self,graph=False): if graph: return self._models else: return self._models.vertices() def dimension(self): return self._ambiant_space.dimension() def indices_basis(self): return self._ambiant_space.indices_basis()
def GeneralizedDeBruijn(self, n, d): r""" Returns the generalized de Bruijn digraph of order `n` and degree `d`. The generalized de Bruijn digraph has been defined in [RPK80]_ [RPK83]_. It has vertex set `V=\{0, 1,..., n-1\}` and there is an arc from vertex `u \in V` to all vertices `v \in V` such that `v \equiv (u*d + a) \mod{n}` with `0 \leq a < d`. When `n = d^{D}`, the generalized de Bruijn digraph is isomorphic to the de Bruijn digraph of degree `d` and diameter `D`. INPUTS: - ``n`` -- is the number of vertices of the digraph - ``d`` -- is the degree of the digraph .. SEEALSO:: * :meth:`sage.graphs.generic_graph.GenericGraph.is_circulant` -- checks whether a (di)graph is circulant, and/or returns all possible sets of parameters. EXAMPLE:: sage: GB = digraphs.GeneralizedDeBruijn(8, 2) sage: GB.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True) (True, {0: '000', 1: '001', 2: '010', 3: '011', 4: '100', 5: '101', 6: '110', 7: '111'}) TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.GeneralizedDeBruijn(2, 0) Traceback (most recent call last): ... ValueError: The generalized de Bruijn digraph is defined for degree at least one. An exception is raised when the order of the graph is less than one:: sage: G = digraphs.GeneralizedDeBruijn(0, 2) Traceback (most recent call last): ... ValueError: The generalized de Bruijn digraph is defined for at least one vertex. REFERENCES: .. [RPK80] S. M. Reddy, D. K. Pradhan, and J. Kuhl. Directed graphs with minimal diameter and maximal connectivity, School Eng., Oakland Univ., Rochester MI, Tech. Rep., July 1980. .. [RPK83] S. Reddy, P. Raghavan, and J. Kuhl. A Class of Graphs for Processor Interconnection. *IEEE International Conference on Parallel Processing*, pages 154-157, Los Alamitos, Ca., USA, August 1983. """ if n < 1: raise ValueError( "The generalized de Bruijn digraph is defined for at least one vertex." ) if d < 1: raise ValueError( "The generalized de Bruijn digraph is defined for degree at least one." ) GB = DiGraph(loops=True) GB.allow_multiple_edges(True) for u in xrange(n): for a in xrange(u * d, u * d + d): GB.add_edge(u, a % n) GB.name("Generalized de Bruijn digraph (n=%s, d=%s)" % (n, d)) return GB
def Hasse_diagram_from_incidences(atom_to_coatoms, coatom_to_atoms, face_constructor=None, required_atoms=None, key = None, **kwds): r""" Compute the Hasse diagram of an atomic and coatomic lattice. INPUT: - ``atom_to_coatoms`` -- list, ``atom_to_coatom[i]`` should list all coatoms over the ``i``-th atom; - ``coatom_to_atoms`` -- list, ``coatom_to_atom[i]`` should list all atoms under the ``i``-th coatom; - ``face_constructor`` -- function or class taking as the first two arguments sorted :class:`tuple` of integers and any keyword arguments. It will be called to construct a face over atoms passed as the first argument and under coatoms passed as the second argument. Default implementation will just return these two tuples as a tuple; - ``required_atoms`` -- list of atoms (default:None). Each non-empty "face" requires at least on of the specified atoms present. Used to ensure that each face has a vertex. - ``key`` -- any hashable value (default: None). It is passed down to :class:`~sage.combinat.posets.posets.FinitePoset`. - all other keyword arguments will be passed to ``face_constructor`` on each call. OUTPUT: - :class:`finite poset <sage.combinat.posets.posets.FinitePoset>` with elements constructed by ``face_constructor``. .. NOTE:: In addition to the specified partial order, finite posets in Sage have internal total linear order of elements which extends the partial one. This function will try to make this internal order to start with the bottom and atoms in the order corresponding to ``atom_to_coatoms`` and to finish with coatoms in the order corresponding to ``coatom_to_atoms`` and the top. This may not be possible if atoms and coatoms are the same, in which case the preference is given to the first list. ALGORITHM: The detailed description of the used algorithm is given in [KP2002]_. The code of this function follows the pseudo-code description in the section 2.5 of the paper, although it is mostly based on frozen sets instead of sorted lists - this makes the implementation easier and should not cost a big performance penalty. (If one wants to make this function faster, it should be probably written in Cython.) While the title of the paper mentions only polytopes, the algorithm (and the implementation provided here) is applicable to any atomic and coatomic lattice if both incidences are given, see Section 3.4. In particular, this function can be used for strictly convex cones and complete fans. REFERENCES: [KP2002]_ AUTHORS: - Andrey Novoseltsev (2010-05-13) with thanks to Marshall Hampton for the reference. EXAMPLES: Let's construct the Hasse diagram of a lattice of subsets of {0, 1, 2}. Our atoms are {0}, {1}, and {2}, while our coatoms are {0,1}, {0,2}, and {1,2}. Then incidences are :: sage: atom_to_coatoms = [(0,1), (0,2), (1,2)] sage: coatom_to_atoms = [(0,1), (0,2), (1,2)] and we can compute the Hasse diagram as :: sage: L = sage.geometry.cone.Hasse_diagram_from_incidences( ....: atom_to_coatoms, coatom_to_atoms) sage: L Finite poset containing 8 elements with distinguished linear extension sage: for level in L.level_sets(): print(level) [((), (0, 1, 2))] [((0,), (0, 1)), ((1,), (0, 2)), ((2,), (1, 2))] [((0, 1), (0,)), ((0, 2), (1,)), ((1, 2), (2,))] [((0, 1, 2), ())] For more involved examples see the *source code* of :meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.face_lattice` and :meth:`sage.geometry.fan.RationalPolyhedralFan._compute_cone_lattice`. """ from sage.graphs.digraph import DiGraph from sage.combinat.posets.posets import FinitePoset def default_face_constructor(atoms, coatoms, **kwds): return (atoms, coatoms) if face_constructor is None: face_constructor = default_face_constructor atom_to_coatoms = [frozenset(atc) for atc in atom_to_coatoms] A = frozenset(range(len(atom_to_coatoms))) # All atoms coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms] C = frozenset(range(len(coatom_to_atoms))) # All coatoms # Comments with numbers correspond to steps in Section 2.5 of the article L = DiGraph(1) # 3: initialize L faces = dict() atoms = frozenset() coatoms = C faces[atoms, coatoms] = 0 next_index = 1 Q = [(atoms, coatoms)] # 4: initialize Q with the empty face while Q: # 5 q_atoms, q_coatoms = Q.pop() # 6: remove some q from Q q = faces[q_atoms, q_coatoms] # 7: compute H = {closure(q+atom) : atom not in atoms of q} H = dict() candidates = set(A.difference(q_atoms)) for atom in candidates: coatoms = q_coatoms.intersection(atom_to_coatoms[atom]) atoms = A for coatom in coatoms: atoms = atoms.intersection(coatom_to_atoms[coatom]) H[atom] = (atoms, coatoms) # 8: compute the set G of minimal sets in H minimals = set([]) while candidates: candidate = candidates.pop() atoms = H[candidate][0] if atoms.isdisjoint(candidates) and atoms.isdisjoint(minimals): minimals.add(candidate) # Now G == {H[atom] : atom in minimals} for atom in minimals: # 9: for g in G: g_atoms, g_coatoms = H[atom] if not required_atoms is None: if g_atoms.isdisjoint(required_atoms): continue if (g_atoms, g_coatoms) in faces: g = faces[g_atoms, g_coatoms] else: # 11: if g was newly created g = next_index faces[g_atoms, g_coatoms] = g next_index += 1 Q.append((g_atoms, g_coatoms)) # 12 L.add_edge(q, g) # 14 # End of algorithm, now construct a FinitePoset. # In principle, it is recommended to use Poset or in this case perhaps # even LatticePoset, but it seems to take several times more time # than the above computation, makes unnecessary copies, and crashes. # So for now we will mimic the relevant code from Poset. # Enumeration of graph vertices must be a linear extension of the poset new_order = L.topological_sort() # Make sure that coatoms are in the end in proper order tail = [faces[atoms, frozenset([coatom])] for coatom, atoms in enumerate(coatom_to_atoms)] tail.append(faces[A, frozenset()]) new_order = [n for n in new_order if n not in tail] + tail # Make sure that atoms are in the beginning in proper order head = [0] # We know that the empty face has index 0 head.extend(faces[frozenset([atom]), coatoms] for atom, coatoms in enumerate(atom_to_coatoms) if required_atoms is None or atom in required_atoms) new_order = head + [n for n in new_order if n not in head] # "Invert" this list to a dictionary labels = dict() for new, old in enumerate(new_order): labels[old] = new L.relabel(labels) # Construct the actual poset elements elements = [None] * next_index for face, index in faces.items(): atoms, coatoms = face elements[labels[index]] = face_constructor( tuple(sorted(atoms)), tuple(sorted(coatoms)), **kwds) D = {i:f for i,f in enumerate(elements)} L.relabel(D) return FinitePoset(L, elements, key = key)
def GeneralizedDeBruijn(self, n, d): r""" Returns the generalized de Bruijn digraph of order `n` and degree `d`. The generalized de Bruijn digraph has been defined in [RPK80]_ [RPK83]_. It has vertex set `V=\{0, 1,..., n-1\}` and there is an arc from vertex `u \in V` to all vertices `v \in V` such that `v \equiv (u*d + a) \mod{n}` with `0 \leq a < d`. When `n = d^{D}`, the generalized de Bruijn digraph is isomorphic to the de Bruijn digraph of degree `d` and diameter `D`. INPUTS: - ``n`` -- is the number of vertices of the digraph - ``d`` -- is the degree of the digraph .. SEEALSO:: * :meth:`sage.graphs.generic_graph.GenericGraph.is_circulant` -- checks whether a (di)graph is circulant, and/or returns all possible sets of parameters. EXAMPLE:: sage: GB = digraphs.GeneralizedDeBruijn(8, 2) sage: GB.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True) (True, {0: '000', 1: '001', 2: '010', 3: '011', 4: '100', 5: '101', 6: '110', 7: '111'}) TESTS: An exception is raised when the degree is less than one:: sage: G = digraphs.GeneralizedDeBruijn(2, 0) Traceback (most recent call last): ... ValueError: The generalized de Bruijn digraph is defined for degree at least one. An exception is raised when the order of the graph is less than one:: sage: G = digraphs.GeneralizedDeBruijn(0, 2) Traceback (most recent call last): ... ValueError: The generalized de Bruijn digraph is defined for at least one vertex. REFERENCES: .. [RPK80] S. M. Reddy, D. K. Pradhan, and J. Kuhl. Directed graphs with minimal diameter and maximal connectivity, School Eng., Oakland Univ., Rochester MI, Tech. Rep., July 1980. .. [RPK83] S. Reddy, P. Raghavan, and J. Kuhl. A Class of Graphs for Processor Interconnection. *IEEE International Conference on Parallel Processing*, pages 154-157, Los Alamitos, Ca., USA, August 1983. """ if n < 1: raise ValueError("The generalized de Bruijn digraph is defined for at least one vertex.") if d < 1: raise ValueError("The generalized de Bruijn digraph is defined for degree at least one.") GB = DiGraph(loops = True) GB.allow_multiple_edges(True) for u in xrange(n): for a in xrange(u*d, u*d+d): GB.add_edge(u, a%n) GB.name( "Generalized de Bruijn digraph (n=%s, d=%s)"%(n,d) ) return GB
def DeBruijn(self, k, n, vertices='strings'): r""" Returns the De Bruijn digraph with parameters `k,n`. The De Bruijn digraph with parameters `k,n` is built upon a set of vertices equal to the set of words of length `n` from a dictionary of `k` letters. In this digraph, there is an arc `w_1w_2` if `w_2` can be obtained from `w_1` by removing the leftmost letter and adding a new letter at its right end. For more information, see the :wikipedia:`Wikipedia article on De Bruijn graph <De_Bruijn_graph>`. INPUT: - ``k`` -- Two possibilities for this parameter : - An integer equal to the cardinality of the alphabet to use, that is the degree of the digraph to be produced. - An iterable object to be used as the set of letters. The degree of the resulting digraph is the cardinality of the set of letters. - ``n`` -- An integer equal to the length of words in the De Bruijn digraph when ``vertices == 'strings'``, and also to the diameter of the digraph. - ``vertices`` -- 'strings' (default) or 'integers', specifying whether the vertices are words build upon an alphabet or integers. EXAMPLES:: sage: db=digraphs.DeBruijn(2,2); db De Bruijn digraph (k=2, n=2): Looped digraph on 4 vertices sage: db.order() 4 sage: db.size() 8 TESTS:: sage: digraphs.DeBruijn(5,0) De Bruijn digraph (k=5, n=0): Looped multi-digraph on 1 vertex sage: digraphs.DeBruijn(0,0) De Bruijn digraph (k=0, n=0): Looped multi-digraph on 0 vertices """ from sage.combinat.words.words import Words from sage.rings.integer import Integer W = Words(range(k) if isinstance(k, Integer) else k, n) A = Words(range(k) if isinstance(k, Integer) else k, 1) g = DiGraph(loops=True) if vertices == 'strings': if n == 0: g.allow_multiple_edges(True) v = W[0] for a in A: g.add_edge(v.string_rep(), v.string_rep(), a.string_rep()) else: for w in W: ww = w[1:] for a in A: g.add_edge(w.string_rep(), (ww * a).string_rep(), a.string_rep()) else: d = W.size_of_alphabet() g = digraphs.GeneralizedDeBruijn(d**n, d) g.name("De Bruijn digraph (k=%s, n=%s)" % (k, n)) return g