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 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 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 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, 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 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 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 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 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 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 cayley_graph(self, side="right", simple=False, elements=None, generators=None, connecting_set=None): r""" Return the Cayley graph for this finite semigroup. INPUT: - ``side`` -- "left", "right", or "twosided": the side on which the generators act (default:"right") - ``simple`` -- boolean (default:False): if True, returns a simple graph (no loops, no labels, no multiple edges) - ``generators`` -- a list, tuple, or family of elements of ``self`` (default: ``self.semigroup_generators()``) - ``connecting_set`` -- alias for ``generators``; deprecated - ``elements`` -- a list (or iterable) of elements of ``self`` OUTPUT: - :class:`DiGraph` EXAMPLES: We start with the (right) Cayley graphs of some classical groups:: sage: D4 = DihedralGroup(4); D4 Dihedral group of order 8 as a permutation group sage: G = D4.cayley_graph() sage: show(G, color_by_label=True, edge_labels=True) sage: A5 = AlternatingGroup(5); A5 Alternating group of order 5!/2 as a permutation group sage: G = A5.cayley_graph() sage: G.show3d(color_by_label=True, edge_size=0.01, edge_size2=0.02, vertex_size=0.03) sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, xres=700, yres=700, iterations=200) # long time (less than a minute) sage: G.num_edges() 120 sage: w = WeylGroup(['A',3]) sage: d = w.cayley_graph(); d Digraph on 24 vertices sage: d.show3d(color_by_label=True, edge_size=0.01, vertex_size=0.03) Alternative generators may be specified:: sage: G = A5.cayley_graph(generators=[A5.gens()[0]]) sage: G.num_edges() 60 sage: g=PermutationGroup([(i+1,j+1) for i in range(5) for j in range(5) if j!=i]) sage: g.cayley_graph(generators=[(1,2),(2,3)]) Digraph on 120 vertices If ``elements`` is specified, then only the subgraph induced and those elements is returned. Here we use it to display the Cayley graph of the free monoid truncated on the elements of length at most 3:: sage: M = Monoids().example(); M An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') sage: elements = [ M.prod(w) for w in sum((list(Words(M.semigroup_generators(),k)) for k in range(4)),[]) ] sage: G = M.cayley_graph(elements = elements) sage: G.num_verts(), G.num_edges() (85, 84) sage: G.show3d(color_by_label=True, edge_size=0.001, vertex_size=0.01) We now illustrate the ``side`` and ``simple`` options on a semigroup:: sage: S = FiniteSemigroups().example(alphabet=('a','b')) sage: g = S.cayley_graph(simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('b', 'ba', None)] :: sage: g = S.cayley_graph(side="left", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('b', 'ba', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided") sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'a', (0, 'left')), ('a', 'a', (0, 'right')), ('a', 'ab', (1, 'right')), ('a', 'ba', (1, 'left')), ('ab', 'ab', (0, 'left')), ('ab', 'ab', (0, 'right')), ('ab', 'ab', (1, 'right')), ('ab', 'ba', (1, 'left')), ('b', 'ab', (0, 'left')), ('b', 'b', (1, 'left')), ('b', 'b', (1, 'right')), ('b', 'ba', (0, 'right')), ('ba', 'ab', (0, 'left')), ('ba', 'ba', (0, 'right')), ('ba', 'ba', (1, 'left')), ('ba', 'ba', (1, 'right'))] :: sage: s1 = SymmetricGroup(1); s = s1.cayley_graph(); s.vertices() [()] TESTS:: sage: SymmetricGroup(2).cayley_graph(side="both") Traceback (most recent call last): ... ValueError: option 'side' must be 'left', 'right' or 'twosided' .. TODO:: - Add more options for constructing subgraphs of the Cayley graph, handling the standard use cases when exploring large/infinite semigroups (a predicate, generators of an ideal, a maximal length in term of the generators) - Specify good default layout/plot/latex options in the graph - Generalize to combinatorial modules with module generators / operators AUTHORS: - Bobby Moretti (2007-08-10) - Robert Miller (2008-05-01): editing - Nicolas M. Thiery (2008-12): extension to semigroups, ``side``, ``simple``, and ``elements`` options, ... """ from sage.graphs.digraph import DiGraph from monoids import Monoids from groups import Groups if not side in ["left", "right", "twosided"]: raise ValueError( "option 'side' must be 'left', 'right' or 'twosided'") if elements is None: assert self.is_finite( ), "elements should be specified for infinite semigroups" elements = self else: elements = set(elements) if simple or self in Groups(): result = DiGraph() else: result = DiGraph(multiedges=True, loops=True) result.add_vertices(elements) if connecting_set is not None: generators = connecting_set if generators is None: if self in Monoids and hasattr(self, "monoid_generators"): generators = self.monoid_generators() else: generators = self.semigroup_generators() if isinstance(generators, (list, tuple)): generators = dict((self(g), self(g)) for g in generators) left = (side == "left" or side == "twosided") right = (side == "right" or side == "twosided") def add_edge(source, target, label, side_label): """ Skips edges whose targets are not in elements Return an appropriate edge given the options """ if (elements is not self and target not in elements): return if simple: result.add_edge([source, target]) elif side == "twosided": result.add_edge([source, target, (label, side_label)]) else: result.add_edge([source, target, label]) for x in elements: for i in generators.keys(): if left: add_edge(x, generators[i] * x, i, "left") if right: add_edge(x, x * generators[i], i, "right") return result
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 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 cayley_graph(self, side="right", simple=False, elements = None, generators = None, connecting_set = None): r""" Return the Cayley graph for this finite semigroup. INPUT: - ``side`` -- "left", "right", or "twosided": the side on which the generators act (default:"right") - ``simple`` -- boolean (default:False): if True, returns a simple graph (no loops, no labels, no multiple edges) - ``generators`` -- a list, tuple, or family of elements of ``self`` (default: ``self.semigroup_generators()``) - ``connecting_set`` -- alias for ``generators``; deprecated - ``elements`` -- a list (or iterable) of elements of ``self`` OUTPUT: - :class:`DiGraph` EXAMPLES: We start with the (right) Cayley graphs of some classical groups:: sage: D4 = DihedralGroup(4); D4 Dihedral group of order 8 as a permutation group sage: G = D4.cayley_graph() sage: show(G, color_by_label=True, edge_labels=True) sage: A5 = AlternatingGroup(5); A5 Alternating group of order 5!/2 as a permutation group sage: G = A5.cayley_graph() sage: G.show3d(color_by_label=True, edge_size=0.01, edge_size2=0.02, vertex_size=0.03) sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, xres=700, yres=700, iterations=200) # long time (less than a minute) sage: G.num_edges() 120 sage: w = WeylGroup(['A',3]) sage: d = w.cayley_graph(); d Digraph on 24 vertices sage: d.show3d(color_by_label=True, edge_size=0.01, vertex_size=0.03) Alternative generators may be specified:: sage: G = A5.cayley_graph(generators=[A5.gens()[0]]) sage: G.num_edges() 60 sage: g=PermutationGroup([(i+1,j+1) for i in range(5) for j in range(5) if j!=i]) sage: g.cayley_graph(generators=[(1,2),(2,3)]) Digraph on 120 vertices If ``elements`` is specified, then only the subgraph induced and those elements is returned. Here we use it to display the Cayley graph of the free monoid truncated on the elements of length at most 3:: sage: M = Monoids().example(); M An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') sage: elements = [ M.prod(w) for w in sum((list(Words(M.semigroup_generators(),k)) for k in range(4)),[]) ] sage: G = M.cayley_graph(elements = elements) sage: G.num_verts(), G.num_edges() (85, 84) sage: G.show3d(color_by_label=True, edge_size=0.001, vertex_size=0.01) We now illustrate the ``side`` and ``simple`` options on a semigroup:: sage: S = FiniteSemigroups().example(alphabet=('a','b')) sage: g = S.cayley_graph(simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('b', 'ba', None)] :: sage: g = S.cayley_graph(side="left", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('b', 'ba', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided") sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'a', (0, 'left')), ('a', 'a', (0, 'right')), ('a', 'ab', (1, 'right')), ('a', 'ba', (1, 'left')), ('ab', 'ab', (0, 'left')), ('ab', 'ab', (0, 'right')), ('ab', 'ab', (1, 'right')), ('ab', 'ba', (1, 'left')), ('b', 'ab', (0, 'left')), ('b', 'b', (1, 'left')), ('b', 'b', (1, 'right')), ('b', 'ba', (0, 'right')), ('ba', 'ab', (0, 'left')), ('ba', 'ba', (0, 'right')), ('ba', 'ba', (1, 'left')), ('ba', 'ba', (1, 'right'))] :: sage: s1 = SymmetricGroup(1); s = s1.cayley_graph(); s.vertices() [()] TESTS:: sage: SymmetricGroup(2).cayley_graph(side="both") Traceback (most recent call last): ... ValueError: option 'side' must be 'left', 'right' or 'twosided' .. TODO:: - Add more options for constructing subgraphs of the Cayley graph, handling the standard use cases when exploring large/infinite semigroups (a predicate, generators of an ideal, a maximal length in term of the generators) - Specify good default layout/plot/latex options in the graph - Generalize to combinatorial modules with module generators / operators AUTHORS: - Bobby Moretti (2007-08-10) - Robert Miller (2008-05-01): editing - Nicolas M. Thiery (2008-12): extension to semigroups, ``side``, ``simple``, and ``elements`` options, ... """ from sage.graphs.digraph import DiGraph from monoids import Monoids from groups import Groups if not side in ["left", "right", "twosided"]: raise ValueError("option 'side' must be 'left', 'right' or 'twosided'") if elements is None: assert self.is_finite(), "elements should be specified for infinite semigroups" elements = self else: elements = set(elements) if simple or self in Groups(): result = DiGraph() else: result = DiGraph(multiedges = True, loops = True) result.add_vertices(elements) if connecting_set is not None: generators = connecting_set if generators is None: if self in Monoids and hasattr(self, "monoid_generators"): generators = self.monoid_generators() else: generators = self.semigroup_generators() if isinstance(generators, (list, tuple)): generators = dict((self(g), self(g)) for g in generators) left = (side == "left" or side == "twosided") right = (side == "right" or side == "twosided") def add_edge(source, target, label, side_label): """ Skips edges whose targets are not in elements Return an appropriate edge given the options """ if (elements is not self and target not in elements): return if simple: result.add_edge([source, target]) elif side == "twosided": result.add_edge([source, target, (label, side_label)]) else: result.add_edge([source, target, label]) for x in elements: for i in generators.keys(): if left: add_edge(x, generators[i]*x, i, "left" ) if right: add_edge(x, x*generators[i], i, "right") return result
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 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 cayley_graph(self, simple = False, elements = None, generators = None): """ Return the Cayley graph of ``self`` EXAMPLES:: sage: M = Semigroups().SetsWithAction().example(); M Representation of the monoid generated by <2,3> acting on Z/10 Z by multiplication sage: G = M.cayley_graph() sage: G.edges() [(0, 0, 2), (0, 0, 3), (1, 2, 2), (1, 3, 3), (2, 4, 2), (2, 6, 3), (3, 6, 2), (3, 9, 3), (4, 2, 3), (4, 8, 2), (5, 0, 2), (5, 5, 3), (6, 2, 2), (6, 8, 3), (7, 1, 3), (7, 4, 2), (8, 4, 3), (8, 6, 2), (9, 7, 3), (9, 8, 2)] Let us look at the graph:: sage: G.set_latex_options(format='dot2tex', edge_labels=True) # optional - requires dot2tex and graphviz sage: view(G, tightpage=True, pdflatex=True) # optional - requires dot2tex and graphviz The Cayley graph as a simple graph (no loops, no labels):: sage: G = M.cayley_graph(simple=True) sage: G.edges() [(1, 2, None), (1, 3, None), (2, 4, None), (2, 6, None), (3, 6, None), (3, 9, None), (4, 2, None), (4, 8, None), (5, 0, None), (6, 2, None), (6, 8, None), (7, 1, None), (7, 4, None), (8, 4, None), (8, 6, None), (9, 7, None), (9, 8, None)] """ # This is duplicated from semigroups.cayley_graph from sage.graphs.digraph import DiGraph if elements is None: assert self._set.is_finite(), "elements should be specified for infinite modules" elements = tuple(self._set) elements_set = set(elements) if simple: result = DiGraph() else: result = DiGraph(multiedges = True, loops = True) result.add_vertices(elements) if generators is None: generators = self.semigroup().semigroup_generators() if isinstance(generators, (list, tuple)): generators = dict((self(g), self(g)) for g in generators) def add_edge(source, target, label): """ Skips edges whose targets are not in elements Return an appropriate edge given the options """ if target not in elements_set: return if simple: if source != target: result.add_edge([source, target]) else: result.add_edge([source, target, label]) for x in elements: for i in generators.keys(): add_edge(x, self._action(generators[i],x), i) return result
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