def _digraph_mutate( dg, k, n, m ): """ Returns a digraph obtained from dg with n+m vertices by mutating at vertex k. INPUT: - ``dg`` -- a digraph with integral edge labels with ``n+m`` vertices - ``k`` -- the vertex at which ``dg`` is mutated EXAMPLES:: sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_mutate sage: dg = ClusterQuiver(['A',4]).digraph() sage: dg.edges() [(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))] sage: _digraph_mutate(dg,2,4,0).edges() [(0, 1, (1, -1)), (1, 2, (1, -1)), (3, 2, (1, -1))] """ edges = dict( ((v1,v2),label) for v1,v2,label in dg._backend.iterator_in_edges(dg,True) ) in_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v2 == k ] out_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v1 == k ] in_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in in_edges ] out_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in out_edges ] diag_edges_new = [] diag_edges_del = [] for (v1,v2,label1) in in_edges: for (w1,w2,label2) in out_edges: l11,l12 = label1 l21,l22 = label2 if (v1,w2) in edges: diag_edges_del.append( (v1,w2,edges[(v1,w2)]) ) a,b = edges[(v1,w2)] a,b = a+l11*l21, b-l12*l22 diag_edges_new.append( (v1,w2,(a,b)) ) elif (w2,v1) in edges: diag_edges_del.append( (w2,v1,edges[(w2,v1)]) ) a,b = edges[(w2,v1)] a,b = b+l11*l21, a-l12*l22 if a<0: diag_edges_new.append( (w2,v1,(b,a)) ) elif a>0: diag_edges_new.append( (v1,w2,(a,b)) ) else: a,b = l11*l21,-l12*l22 diag_edges_new.append( (v1,w2,(a,b)) ) del_edges = in_edges + out_edges + diag_edges_del new_edges = in_edges_new + out_edges_new + diag_edges_new new_edges += [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if not (v1,v2,edges[(v1,v2)]) in del_edges ] dg_new = DiGraph() for v1,v2,label in new_edges: dg_new._backend.add_edge(v1,v2,label,True) if dg_new.order() < n+m: dg_new_vertices = [ v for v in dg_new ] for i in [ v for v in dg if v not in dg_new_vertices ]: dg_new.add_vertex(i) return dg_new
def plot(self, label_elements=True, element_labels=None, label_font_size=12, label_font_color='black', layout="acyclic", **kwds): """ Returns a Graphics object corresponding to the Hasse diagram. EXAMPLES:: sage: uc = [[2,3], [], [1], [1], [1], [3,4]] sage: elm_lbls = Permutations(3).list() sage: P = Poset(uc,elm_lbls) sage: H = P._hasse_diagram sage: levels = H.level_sets() sage: heights = dict([[i, levels[i]] for i in range(len(levels))]) sage: type(H.plot(label_elements=True)) <class 'sage.plot.graphics.Graphics'> :: sage: P = Posets.SymmetricGroupBruhatIntervalPoset([0,1,2,3], [2,3,0,1]) sage: P._hasse_diagram.plot() """ # Set element_labels to default to the vertex set. if element_labels is None: element_labels = range(self.num_verts()) # Create the underlying graph. graph = DiGraph(self) graph.relabel(element_labels) return graph.plot(layout=layout, **kwds)
def plot(self, label_elements=True, element_labels=None, label_font_size=12,label_font_color='black', layout = "acyclic", **kwds): """ Returns a Graphics object corresponding to the Hasse diagram. EXAMPLES:: sage: uc = [[2,3], [], [1], [1], [1], [3,4]] sage: elm_lbls = Permutations(3).list() sage: P = Poset(uc,elm_lbls) sage: H = P._hasse_diagram sage: levels = H.level_sets() sage: heights = dict([[i, levels[i]] for i in range(len(levels))]) sage: type(H.plot(label_elements=True)) <class 'sage.plot.graphics.Graphics'> :: sage: P = Posets.SymmetricGroupBruhatIntervalPoset([0,1,2,3], [2,3,0,1]) sage: P._hasse_diagram.plot() """ # Set element_labels to default to the vertex set. if element_labels is None: element_labels = range(self.num_verts()) # Create the underlying graph. graph = DiGraph(self) graph.relabel(element_labels) return graph.plot(layout = layout, **kwds)
def to_digraph(self, word_labels=False): r""" Returns a ``DiGraph`` object of the transition graph of the suffix tree. INPUT: - ``word_labels`` - boolean (defaut: ``False``) if ``False``, labels the edges by pairs `(i, j)`; if ``True``, labels the edges by ``word[i:j]``. EXAMPLES:: sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree sage: W = Words([0,1,2]) sage: t = ImplicitSuffixTree(W([0,1,0,1,2])) sage: t.to_digraph() Digraph on 8 vertices """ if self._letters == []: d = {0: {}} return DiGraph(d) d = self.transition_function_dictionary() for u in d: for (v, (i, j)) in d[u].iteritems(): if word_labels: d[u][v] = self._word[i:j] elif j == None: d[u][v] = (i, len(self._letters)) return DiGraph(d)
def _matrix_to_digraph( M ): """ Returns the digraph obtained from the matrix ``M``. In order to generate a quiver, we assume that ``M`` is skew-symmetrizable. EXAMPLES:: sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _matrix_to_digraph sage: _matrix_to_digraph(matrix(3,[0,1,0,-1,0,-1,0,1,0])) Digraph on 3 vertices """ n = M.ncols() dg = DiGraph(sparse=True) for i,j in M.nonzero_positions(): if i >= n: a,b = M[i,j],-M[i,j] else: a,b = M[i,j],M[j,i] if a > 0: dg._backend.add_edge(i,j,(a,b),True) elif i >= n: dg._backend.add_edge(j,i,(-a,-b),True) if dg.order() < M.nrows(): for i in [ index for index in xrange(M.nrows()) if index not in dg ]: dg.add_vertex(i) return dg
def __init__(self): """ EXAMPLES:: sage: C = sage.categories.examples.crystals.NaiveCrystal() sage: C == Crystals().example(choice='naive') True """ Parent.__init__(self, category = ClassicalCrystals()) self.n = 2 self._cartan_type = CartanType(['A',2]) self.G = DiGraph(5) self.G.add_edges([ [0,1,1], [1,2,1], [2,3,1], [3,5,1], [0,4,2], [4,5,2] ]) self.module_generators = [ self(0) ]
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 cayley_graph_disabled(self, connecting_set=None): """ AUTHORS: - Bobby Moretti (2007-08-10) - Robert Miller (2008-05-01): editing """ if connecting_set is None: connecting_set = self.gens() else: try: for g in connecting_set: assert g in self except AssertionError: raise RuntimeError( "Each element of the connecting set must be in the group!" ) connecting_set = [self(g) for g in connecting_set] from sage.graphs.all import DiGraph arrows = {} for x in self: arrows[x] = {} for g in connecting_set: xg = x * g # cache the multiplication if not xg == x: arrows[x][xg] = g return DiGraph(arrows, implementation='networkx')
def digraph(self): """ Returns a directed graph where the vertices are the individual species that make up this one. EXAMPLES:: sage: X = species.SingletonSpecies() sage: B = species.CombinatorialSpecies() sage: B.define(X+B*B) sage: g = B.digraph(); g Multi-digraph on 4 vertices :: sage: g_c, labels = g.canonical_label(certify=True) sage: g.relabel() sage: g_r = g.canonical_label() sage: g_c == g_r True sage: list(sorted(labels.keys())) [Combinatorial species, Product of (Combinatorial species) and (Combinatorial species), Singleton species, Sum of (Singleton species) and (Product of (Combinatorial species) and (Combinatorial species))] sage: list(sorted(labels.values())) [0, 1, 2, 3] """ from sage.graphs.all import DiGraph d = DiGraph(multiedges=True) self._add_to_digraph(d) return d
def __getitem__(self, i): r""" With a tuple (i,j) as argument, returns the scalar product `\langle \alpha^\vee_i, \alpha_j\rangle`. Otherwise, behaves as the usual DiGraph.__getitem__ EXAMPLES: We use the `C_4` dynkin diagram as a cartan matrix:: sage: g = DynkinDiagram(['C',4]) sage: matrix([[g[i,j] for j in range(1,5)] for i in range(1,5)]) [ 2 -1 0 0] [-1 2 -1 0] [ 0 -1 2 -2] [ 0 0 -1 2] The neighbors of a node can still be obtained in the usual way:: sage: [g[i] for i in range(1,5)] [[2], [1, 3], [2, 4], [3]] """ if not isinstance(i, tuple): return DiGraph.__getitem__(self, i) [i, j] = i if i == j: return 2 elif self.has_edge(j, i): return -self.edge_label(j, i) else: return 0
def __getitem__(self, i): r""" With a tuple (i,j) as argument, returns the scalar product `\langle \alpha^\vee_i, \alpha_j\rangle`. Otherwise, behaves as the usual DiGraph.__getitem__ EXAMPLES: We use the `C_4` dynkin diagram as a cartan matrix:: sage: g = DynkinDiagram(['C',4]) sage: matrix([[g[i,j] for j in range(1,5)] for i in range(1,5)]) [ 2 -1 0 0] [-1 2 -1 0] [ 0 -1 2 -2] [ 0 0 -1 2] The neighbors of a node can still be obtained in the usual way:: sage: [g[i] for i in range(1,5)] [[2], [1, 3], [2, 4], [3]] """ if not isinstance(i, tuple): return DiGraph.__getitem__(self,i) [i,j] = i if i == j: return 2 elif self.has_edge(j, i): return -self.edge_label(j, i) else: return 0
def digraph(self): """ Returns the DiGraph associated to self. EXAMPLES:: sage: C = Crystals().example(5) sage: C.digraph() Digraph on 6 vertices The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, and green for edge 3:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz One may also overwrite the colors:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"}) sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz Or one may add colors to yet unspecified edges:: sage: C = Crystals().example(4) sage: G = C.digraph() sage: C.cartan_type()._index_set_coloring[4]="purple" sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz TODO: add more tests """ from sage.graphs.all import DiGraph d = {} for x in self: d[x] = {} for i in self.index_set(): child = x.f(i) if child is None: continue d[x][child]=i G = DiGraph(d) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = self.cartan_type()._index_set_coloring, edge_options = lambda (u,v,label): ({"backward":label ==0})) return G
def __init__(self, t=None): """ INPUT: - ``t`` - a Cartan type or None EXAMPLES:: sage: d = DynkinDiagram(["A", 3]) sage: d == loads(dumps(d)) True Implementation note: if a Cartan type is given, then the nodes are initialized from the index set of this Cartan type. """ DiGraph.__init__(self) self._cartan_type = t if t is not None: self.add_vertices(t.index_set())
def __init__(self, t = None): """ INPUT: - ``t`` - a Cartan type or None EXAMPLES:: sage: d = DynkinDiagram(["A", 3]) sage: d == loads(dumps(d)) True Implementation note: if a Cartan type is given, then the nodes are initialized from the index set of this Cartan type. """ DiGraph.__init__(self) self._cartan_type = t if t is not None: self.add_vertices(t.index_set())
def _dig6_to_digraph( dig6 ): """ Returns the digraph obtained from the dig6 and edge data. INPUT: - ``dig6`` -- a pair ``(dig6, edges)`` where ``dig6`` is a string encoding a digraph and ``edges`` is a dict or tuple encoding edges EXAMPLES:: sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6 sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_digraph sage: dg = ClusterQuiver(['A',4]).digraph() sage: data = _digraph_to_dig6(dg) sage: _dig6_to_digraph(data) Digraph on 4 vertices sage: _dig6_to_digraph(data).edges() [(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))] """ dig6, edges = dig6 dg = DiGraph( dig6 ) if not type(edges) == dict: edges = dict( edges ) for edge in dg._backend.iterator_in_edges(dg,False): if edge in edges: dg.set_edge_label( edge[0],edge[1],edges[edge] ) else: dg.set_edge_label( edge[0],edge[1], (1,-1) ) return dg
def IntegerPartitions(n): """ Returns the poset of integer partitions on the integer ``n``. A partition of a positive integer `n` is a non-increasing list of positive integers that sum to `n`. If `p` and `q` are integer partitions of `n`, then `p` covers `q` if and only if `q` is obtained from `p` by joining two parts of `p` (and sorting, if necessary). EXAMPLES:: sage: P = Posets.IntegerPartitions(7); P Finite poset containing 15 elements sage: len(P.cover_relations()) 28 """ def lower_covers(partition): r""" Nested function for computing the lower covers of elements in the poset of integer partitions. """ lc = [] for i in range(0, len(partition) - 1): for j in range(i + 1, len(partition)): new_partition = partition[:] del new_partition[j] del new_partition[i] new_partition.append(partition[i] + partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import partitions_list H = DiGraph( dict([[tuple(p), lower_covers(p)] for p in partitions_list(n)])) return Poset(H.reverse())
def IntegerPartitions(n): """ Returns the poset of integer partitions on the integer ``n``. A partition of a positive integer `n` is a non-increasing list of positive integers that sum to `n`. If `p` and `q` are integer partitions of `n`, then `p` covers `q` if and only if `q` is obtained from `p` by joining two parts of `p` (and sorting, if necessary). EXAMPLES:: sage: P = Posets.IntegerPartitions(7); P Finite poset containing 15 elements sage: len(P.cover_relations()) 28 """ def lower_covers(partition): r""" Nested function for computing the lower covers of elements in the poset of integer partitions. """ lc = [] for i in range(0,len(partition)-1): for j in range(i+1,len(partition)): new_partition = partition[:] del new_partition[j] del new_partition[i] new_partition.append(partition[i]+partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import partitions_list H = DiGraph(dict([[tuple(p),lower_covers(p)] for p in partitions_list(n)])) return Poset(H.reverse())
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 RestrictedIntegerPartitions(n): """ Returns the poset of integer partitions on the integer `n` ordered by restricted refinement. That is, if `p` and `q` are integer partitions of `n`, then `p` covers `q` if and only if `q` is obtained from `p` by joining two distinct parts of `p` (and sorting, if necessary). EXAMPLES:: sage: P = Posets.RestrictedIntegerPartitions(7); P Finite poset containing 15 elements sage: len(P.cover_relations()) 17 """ def lower_covers(partition): r""" Nested function for computing the lower covers of elements in the restricted poset of integer partitions. """ lc = [] for i in range(0,len(partition)-1): for j in range(i+1,len(partition)): if partition[i] != partition[j]: new_partition = partition[:] del new_partition[j] del new_partition[i] new_partition.append(partition[i]+partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import Partitions H = DiGraph(dict([[tuple(p),lower_covers(p)] for p in Partitions(n)])) return Poset(H.reverse())
def RestrictedIntegerPartitions(n): """ Returns the poset of integer partitions on the integer `n` ordered by restricted refinement. That is, if `p` and `q` are integer partitions of `n`, then `p` covers `q` if and only if `q` is obtained from `p` by joining two distinct parts of `p` (and sorting, if necessary). EXAMPLES:: sage: P = Posets.RestrictedIntegerPartitions(7); P Finite poset containing 15 elements sage: len(P.cover_relations()) 17 """ def lower_covers(partition): r""" Nested function for computing the lower covers of elements in the restricted poset of integer partitions. """ lc = [] for i in range(0, len(partition) - 1): for j in range(i + 1, len(partition)): if partition[i] != partition[j]: new_partition = partition[:] del new_partition[j] del new_partition[i] new_partition.append(partition[i] + partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import Partitions H = DiGraph(dict([[tuple(p), lower_covers(p)] for p in Partitions(n)])) return Poset(H.reverse())
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 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 to_digraph(self): r""" Returns a ``DiGraph`` object of the transition graph of the suffix trie. EXAMPLES:: sage: from sage.combinat.words.suffix_trees import SuffixTrie sage: w = Words("cao")("cac") sage: t = SuffixTrie(w) sage: d = t.to_digraph(); d Digraph on 6 vertices sage: d.adjacency_matrix() [0 1 0 1 0 0] [0 0 1 0 0 0] [0 0 0 0 1 0] [0 0 0 0 0 1] [0 0 0 0 0 0] [0 0 0 0 0 0] """ dag = {} for ((u, letter), v) in self._transition_function.iteritems(): dag.setdefault(u, {})[v] = letter return DiGraph(dag)
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 """ try: n = Integer(n) except: 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: 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.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 _digraph_mutate(dg, k, frozen=None): """ Return a digraph obtained from ``dg`` by mutating at vertex ``k``. Vertices can be labelled by anything, and frozen vertices must be explicitly given. INPUT: - ``dg`` -- a digraph with integral edge labels with ``n+m`` vertices - ``k`` -- the vertex at which ``dg`` is mutated - ``frozen`` -- the list of frozen vertices (default is the empty list) EXAMPLES:: sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_mutate sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver sage: dg = ClusterQuiver(['A',4]).digraph() sage: dg.edges() [(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))] sage: _digraph_mutate(dg,2).edges() [(0, 1, (1, -1)), (1, 2, (1, -1)), (3, 2, (1, -1))] TESTS:: sage: dg = DiGraph([('a','b',(1,-1)),('c','a',(1,-1))]) sage: _digraph_mutate(dg,'a').edges() [('a', 'c', (1, -1)), ('b', 'a', (1, -1)), ('c', 'b', (1, -1))] sage: _digraph_mutate(dg,'a',frozen=['b','c']).edges() [('a', 'c', (1, -1)), ('b', 'a', (1, -1))] sage: dg = DiGraph([('a','b',(2,-2)),('c','a',(2,-2)),('b','c',(2,-2))]) sage: _digraph_mutate(dg,'a').edges() [('a', 'c', (2, -2)), ('b', 'a', (2, -2)), ('c', 'b', (2, -2))] """ # assert sorted(list(dg)) == list(range(n + m)) # this is not assumed anymore if frozen is None: frozen = [] edge_it = dg.incoming_edge_iterator(dg, True) edges = {(v1, v2): label for v1, v2, label in edge_it} edge_it = dg.incoming_edge_iterator([k], True) in_edges = [(v1, v2, label) for v1, v2, label in edge_it] edge_it = dg.outgoing_edge_iterator([k], True) out_edges = [(v1, v2, label) for v1, v2, label in edge_it] in_edges_new = [(v2, v1, (-label[1], -label[0])) for (v1, v2, label) in in_edges] out_edges_new = [(v2, v1, (-label[1], -label[0])) for (v1, v2, label) in out_edges] diag_edges_new = [] diag_edges_del = [] for (v1, v2, label1) in in_edges: l11, l12 = label1 for (w1, w2, label2) in out_edges: if v1 in frozen and w2 in frozen: continue l21, l22 = label2 if (v1, w2) in edges: diag_edges_del.append((v1, w2)) a, b = edges[(v1, w2)] a, b = a + l11 * l21, b - l12 * l22 diag_edges_new.append((v1, w2, (a, b))) elif (w2, v1) in edges: diag_edges_del.append((w2, v1)) a, b = edges[(w2, v1)] a, b = b + l11 * l21, a - l12 * l22 if a < 0: diag_edges_new.append((w2, v1, (b, a))) elif a > 0: diag_edges_new.append((v1, w2, (a, b))) else: a, b = l11 * l21, -l12 * l22 diag_edges_new.append((v1, w2, (a, b))) del_edges = [tuple(ed[:2]) for ed in in_edges + out_edges] del_edges += diag_edges_del new_edges = in_edges_new + out_edges_new new_edges += diag_edges_new new_edges += [(v1, v2, edges[(v1, v2)]) for (v1, v2) in edges if (v1, v2) not in del_edges] dg_new = DiGraph() dg_new.add_vertices(list(dg)) for v1, v2, label in new_edges: dg_new.add_edge(v1, v2, label) return dg_new
def _is_valid_digraph_edge_set( edges, frozen=0 ): """ Returns True if the input data is the edge set of a digraph for a quiver (no loops, no 2-cycles, edge-labels of the specified format), and returns False otherwise. INPUT: - ``frozen`` -- (integer; default:0) The number of frozen vertices. EXAMPLES:: sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _is_valid_digraph_edge_set sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)]] ) The given digraph has edge labels which are not integral or integral 2-tuples. False sage: _is_valid_digraph_edge_set( [[0,1],[2,3,(1,-1)]] ) True sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)],[3,2,(1,-1)]] ) The given digraph or edge list contains oriented 2-cycles. False """ try: dg = DiGraph() dg.allow_multiple_edges(True) dg.add_edges( edges ) # checks if the digraph contains loops if dg.has_loops(): print "The given digraph or edge list contains loops." return False # checks if the digraph contains oriented 2-cycles if _has_two_cycles( dg ): print "The given digraph or edge list contains oriented 2-cycles." return False # checks if all edge labels are 'None', positive integers or tuples of positive integers if not all( i == None or ( i in ZZ and i > 0 ) or ( type(i) == tuple and len(i) == 2 and i[0] in ZZ and i[1] in ZZ ) for i in dg.edge_labels() ): print "The given digraph has edge labels which are not integral or integral 2-tuples." return False # checks if all edge labels for multiple edges are 'None' or positive integers if dg.has_multiple_edges(): for e in set( dg.multiple_edges(labels=False) ): if not all( i == None or ( i in ZZ and i > 0 ) for i in dg.edge_label( e[0], e[1] ) ): print "The given digraph or edge list contains multiple edges with non-integral labels." return False n = dg.order() - frozen if n < 0: print "The number of frozen variables is larger than the number of vertices." return False if [ e for e in dg.edges(labels=False) if e[0] >= n] <> []: print "The given digraph or edge list contains edges within the frozen vertices." return False return True except StandardError: print "Could not even build a digraph from the input data." return False
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 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] Volker Kaibel and Marc E. Pfetsch, "Computing the Face Lattice of a Polytope from its Vertex-Facet Incidences", Computational Geometry: Theory and Applications, Volume 23, Issue 3 (November 2002), 281-290. Available at http://portal.acm.org/citation.cfm?id=763203 and free of charge at http://arxiv.org/abs/math/0106043 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 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.all 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) return FinitePoset(L, elements, key = key)
class NaiveCrystal(UniqueRepresentation, Parent): r""" This is an example of a "crystal" which does not come from any kind of representation, designed primarily to test the Stembridge local rules with. The crystal has vertices labeled 0 through 5, with 0 the highest weight. The code here could also possibly be generalized to create a class that automatically builds a crystal from an edge-colored digraph, if someone feels adventurous. Currently, only the methods :meth:`highest_weight_vector`, :meth:`e`, and :meth:`f` are guaranteed to work. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: C.highest_weight_vector() 0 """ def __init__(self): """ EXAMPLES:: sage: C = sage.categories.examples.crystals.NaiveCrystal() sage: C == Crystals().example(choice='naive') True """ Parent.__init__(self, category=ClassicalCrystals()) self.n = 2 self._cartan_type = CartanType(['A', 2]) self.G = DiGraph(5) self.G.add_edges([[0, 1, 1], [1, 2, 1], [2, 3, 1], [3, 5, 1], [0, 4, 2], [4, 5, 2]]) self.module_generators = [self(0)] def __repr__(self): """ EXAMPLES:: sage: Crystals().example(choice='naive') A broken crystal, defined by digraph, of dimension five. """ return "A broken crystal, defined by digraph, of dimension five." class Element(ElementWrapper): def e(self, i): r""" Returns the action of `e_i` on ``self``. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: [[c,i,c.e(i)] for i in C.index_set() for c in [C(j) for j in [0..5]] if c.e(i) is not None] [[1, 1, 0], [2, 1, 1], [3, 1, 2], [5, 1, 3], [4, 2, 0], [5, 2, 4]] """ assert i in self.index_set() for edge in self.parent().G.edges(): if edge[1] == int(str(self)) and edge[2] == i: return self.parent()(edge[0]) return None def f(self, i): r""" Returns the action of `f_i` on ``self``. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: [[c,i,c.f(i)] for i in C.index_set() for c in [C(j) for j in [0..5]] if c.f(i) is not None] [[0, 1, 1], [1, 1, 2], [2, 1, 3], [3, 1, 5], [0, 2, 4], [4, 2, 5]] """ assert i in self.index_set() for edge in self.parent().G.edges_incident(int(str(self))): if edge[2] == i: return self.parent()(edge[1]) return None
def digraph(self, subset=None, index_set=None, depth=None): """ Return the DiGraph associated to ``self``. INPUT: - ``subset`` -- (optional) a subset of vertices for which the digraph should be constructed - ``index_set`` -- (optional) the index set to draw arrows - ``depth`` -- the depth to draw; optional only for finite crystals EXAMPLES:: sage: T = crystals.Tableaux(['A',2], shape=[2,1]) sage: T.digraph() Digraph on 8 vertices sage: S = T.subcrystal(max_depth=2) sage: len(S) 5 sage: G = T.digraph(subset=list(S)) sage: G.is_isomorphic(T.digraph(depth=2), edge_labels=True) True TESTS: The following example demonstrates the speed improvement. The speedup in non-affine types is small however:: sage: depth = 5 sage: C = crystals.AlcovePaths(['A',2,1], [1,1,0]) sage: general_digraph = Crystals().parent_class.digraph sage: S = C.subcrystal(max_depth=depth, direction='lower') sage: %timeit C.digraph(depth=depth) # not tested 10 loops, best of 3: 48.9 ms per loop sage: %timeit general_digraph(C, subset=S) # not tested 10 loops, best of 3: 96.5 ms per loop sage: G1 = C.digraph(depth=depth) sage: G2 = general_digraph(C, subset=S) sage: G1.is_isomorphic(G2, edge_labels=True) True """ if subset is not None: return Crystals().parent_class.digraph(self, subset, index_set) if self not in Crystals().Finite() and depth is None: raise NotImplementedError( "crystals not known to be finite must" " specify either the subset or depth") from sage.graphs.all import DiGraph if index_set is None: index_set = self.index_set() rank = 0 d = {g: {} for g in self.module_generators} visited = set(d.keys()) while depth is None or rank < depth: recently_visited = set() for x in visited: d.setdefault(x, {}) # does nothing if there's a default for i in index_set: xfi = x.f(i) if xfi is not None: d[x][xfi] = i recently_visited.add(xfi) if not recently_visited: # No new nodes, nothing more to do break rank += 1 visited = recently_visited G = DiGraph(d) if have_dot2tex(): G.set_latex_options( format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) return G
def digraph(self, subset=None, index_set=None, depth=None): """ Return the DiGraph associated to ``self``. INPUT: - ``subset`` -- (optional) a subset of vertices for which the digraph should be constructed - ``index_set`` -- (optional) the index set to draw arrows - ``depth`` -- the depth to draw; optional only for finite crystals EXAMPLES:: sage: T = crystals.Tableaux(['A',2], shape=[2,1]) sage: T.digraph() Digraph on 8 vertices sage: S = T.subcrystal(max_depth=2) sage: len(S) 5 sage: G = T.digraph(subset=list(S)) sage: G.is_isomorphic(T.digraph(depth=2), edge_labels=True) True TESTS: The following example demonstrates the speed improvement. The speedup in non-affine types is small however:: sage: depth = 5 sage: C = crystals.AlcovePaths(['A',2,1], [1,1,0]) sage: general_digraph = Crystals().parent_class.digraph sage: S = C.subcrystal(max_depth=depth, direction='lower') sage: %timeit C.digraph(depth=depth) # not tested 10 loops, best of 3: 48.9 ms per loop sage: %timeit general_digraph(C, subset=S) # not tested 10 loops, best of 3: 96.5 ms per loop sage: G1 = C.digraph(depth=depth) sage: G2 = general_digraph(C, subset=S) sage: G1.is_isomorphic(G2, edge_labels=True) True """ if subset is not None: return Crystals().parent_class.digraph(self, subset, index_set) if self not in Crystals().Finite() and depth is None: raise NotImplementedError("crystals not known to be finite must" " specify either the subset or depth") from sage.graphs.all import DiGraph if index_set is None: index_set = self.index_set() rank = 0 d = {g: {} for g in self.module_generators} visited = set(d.keys()) while depth is None or rank < depth: recently_visited = set() for x in visited: d.setdefault(x, {}) # does nothing if there's a default for i in index_set: xfi = x.f(i) if xfi is not None: d[x][xfi] = i recently_visited.add(xfi) if not recently_visited: # No new nodes, nothing more to do break rank += 1 visited = recently_visited G = DiGraph(d) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) return 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 """ try: n = Integer(n) except: 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: 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.random() < p: D.add_edge(i,j) if not D.is_directed_acyclic(): D.delete_edge(i,j) return Poset(D,cover_relations=False)
class NaiveCrystal(UniqueRepresentation, Parent): r""" This is an example of a "crystal" which does not come from any kind of representation, designed primarily to test the Stembridge local rules with. The crystal has vertices labeled 0 through 5, with 0 the highest weight. The code here could also possibly be generalized to create a class that automatically builds a crystal from an edge-colored digraph, if someone feels adventurous. Currently, only the methods :meth:`highest_weight_vector`, :meth:`e`, and :meth:`f` are guaranteed to work. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: C.highest_weight_vector() 0 """ def __init__(self): """ EXAMPLES:: sage: C = sage.categories.examples.crystals.NaiveCrystal() sage: C == Crystals().example(choice='naive') True """ Parent.__init__(self, category = ClassicalCrystals()) self.n = 2 self._cartan_type = CartanType(['A',2]) self.G = DiGraph(5) self.G.add_edges([ [0,1,1], [1,2,1], [2,3,1], [3,5,1], [0,4,2], [4,5,2] ]) self.module_generators = [ self(0) ] def __repr__(self): """ EXAMPLES:: sage: Crystals().example(choice='naive') A broken crystal, defined by digraph, of dimension five. """ return "A broken crystal, defined by digraph, of dimension five." class Element(ElementWrapper): def e(self, i): r""" Returns the action of `e_i` on ``self``. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: [[c,i,c.e(i)] for i in C.index_set() for c in [C(j) for j in [0..5]] if c.e(i) is not None] [[1, 1, 0], [2, 1, 1], [3, 1, 2], [5, 1, 3], [4, 2, 0], [5, 2, 4]] """ assert i in self.index_set() for edge in self.parent().G.edges(): if edge[1]==int(str(self)) and edge[2]==i: return self.parent()(edge[0]) return None def f(self, i): r""" Returns the action of `f_i` on ``self``. EXAMPLES:: sage: C = Crystals().example(choice='naive') sage: [[c,i,c.f(i)] for i in C.index_set() for c in [C(j) for j in [0..5]] if c.f(i) is not None] [[0, 1, 1], [1, 1, 2], [2, 1, 3], [3, 1, 5], [0, 2, 4], [4, 2, 5]] """ assert i in self.index_set() for edge in self.parent().G.edges_incident(int(str(self))): if edge[2] == i: return self.parent()(edge[1]) return None
def dual_equivalence_graph(self, X=None, index_set=None, directed=True): r""" Return the dual equivalence graph indexed by ``index_set`` on the subset ``X`` of ``self``. Let `b \in B` be an element of weight `0`, so `\varepsilon_j(b) = \varphi_j(b)` for all `j \in I`, where `I` is the indexing set. We say `b'` is an `i`-elementary dual equivalence transformation of `b` (where `i \in I`) if * `\varepsilon_i(b) = 1` and `\varepsilon_{i-1}(b) = 0`, and * `b' = f_{i-1} f_i e_{i-1} e_i b`. We can do the inverse procedure by interchanging `i` and `i-1` above. .. NOTE:: If the index set is not an ordered interval, we let `i - 1` mean the index appearing before `i` in `I`. This definition comes from [As2008]_ Section 4 (where our `\varphi_j(b)` and `\varepsilon_j(b)` are denoted by `\epsilon(b, j)` and `-\delta(b, j)`, respectively). The dual equivalence graph of `B` is defined to be the colored graph whose vertices are the elements of `B` of weight `0`, and whose edges of color `i` (for `i \in I`) connect pairs `\{ b, b' \}` such that `b'` is an `i`-elementary dual equivalence transformation of `b`. .. NOTE:: This dual equivalence graph is a generalization of `\mathcal{G}\left(\mathcal{X}\right)` in [As2008]_ Section 4 except we do not require `\varepsilon_i(b) = 0, 1` for all `i`. This definition can be generalized by choosing a subset `X` of the set of all vertices of `B` of weight `0`, and restricting the dual equivalence graph to the vertex set `X`. INPUT: - ``X`` -- (optional) the vertex set `X` (default: the whole set of vertices of ``self`` of weight `0`) - ``index_set`` -- (optional) the index set `I` (default: the whole index set of ``self``); this has to be a subset of the index set of ``self`` (as a list or tuple) - ``directed`` -- (default: ``True``) whether to have the dual equivalence graph be directed, where the head of an edge `b - b'` is `b` and the tail is `b' = f_{i-1} f_i e_{i-1} e_i b`) .. SEEALSO:: :meth:`sage.combinat.partition.Partition.dual_equivalence_graph` EXAMPLES:: sage: T = crystals.Tableaux(['A',3], shape=[2,2]) sage: G = T.dual_equivalence_graph() sage: sorted(G.edges()) [([[1, 3], [2, 4]], [[1, 2], [3, 4]], 2), ([[1, 2], [3, 4]], [[1, 3], [2, 4]], 3)] sage: T = crystals.Tableaux(['A',4], shape=[3,2]) sage: G = T.dual_equivalence_graph() sage: sorted(G.edges()) [([[1, 3, 5], [2, 4]], [[1, 3, 4], [2, 5]], 4), ([[1, 3, 5], [2, 4]], [[1, 2, 5], [3, 4]], 2), ([[1, 3, 4], [2, 5]], [[1, 2, 4], [3, 5]], 2), ([[1, 2, 5], [3, 4]], [[1, 3, 5], [2, 4]], 3), ([[1, 2, 4], [3, 5]], [[1, 2, 3], [4, 5]], 3), ([[1, 2, 3], [4, 5]], [[1, 2, 4], [3, 5]], 4)] sage: T = crystals.Tableaux(['A',4], shape=[3,1]) sage: G = T.dual_equivalence_graph(index_set=[1,2,3]) sage: G.vertices() [[[1, 3, 4], [2]], [[1, 2, 4], [3]], [[1, 2, 3], [4]]] sage: G.edges() [([[1, 3, 4], [2]], [[1, 2, 4], [3]], 2), ([[1, 2, 4], [3]], [[1, 2, 3], [4]], 3)] TESTS:: sage: T = crystals.Tableaux(['A',4], shape=[3,1]) sage: G = T.dual_equivalence_graph(index_set=[2,3]) sage: sorted(G.edges()) [([[1, 2, 4], [3]], [[1, 2, 3], [4]], 3), ([[2, 4, 5], [3]], [[2, 3, 5], [4]], 3)] sage: sorted(G.vertices()) [[[1, 3, 4], [2]], [[1, 2, 4], [3]], [[2, 4, 5], [3]], [[1, 2, 3], [4]], [[2, 3, 5], [4]], [[1, 1, 1], [5]], [[1, 1, 5], [5]], [[1, 5, 5], [5]], [[2, 3, 4], [5]]] """ if index_set is None: index_set = self.index_set() def wt_zero(x): for i in index_set: if x.epsilon(i) != x.phi(i): return False return True if X is None: X = [x for x in self if wt_zero(x)] checker = lambda x: True elif any(not wt_zero(x) for x in X): raise ValueError("the elements are not all weight 0") else: checker = lambda x: x in X edges = [] for x in X: for k, i in enumerate(index_set[1:]): im = index_set[k] if x.epsilon(i) == 1 and x.epsilon(im) == 0: y = x.e(i).e(im).f(i).f(im) if checker(y): edges.append([x, y, i]) from sage.graphs.all import DiGraph G = DiGraph([X, edges], format="vertices_and_edges", immutable=True) if have_dot2tex(): G.set_latex_options( format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) return G
def digraph(self, subset=None, index_set=None): """ Returns the DiGraph associated to ``self``. INPUT: - ``subset`` -- (Optional) A subset of vertices for which the digraph should be constructed - ``index_set`` -- (Optional) The index set to draw arrows EXAMPLES:: sage: C = Crystals().example(5) sage: C.digraph() Digraph on 6 vertices The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, and green for edge 3:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz One may also overwrite the colors:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"}) sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz Or one may add colors to yet unspecified edges:: sage: C = Crystals().example(4) sage: G = C.digraph() sage: C.cartan_type()._index_set_coloring[4]="purple" sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz Here is an example of how to take the top part up to a given depth of an infinite dimensional crystal:: sage: C = CartanType(['C',2,1]) sage: La = C.root_system().weight_lattice().fundamental_weights() sage: T = HighestWeightCrystal(La[0]) sage: S = T.subcrystal(max_depth=3) sage: G = T.digraph(subset=S); G Digraph on 5 vertices sage: G.vertices() [(1/2*Lambda[0] + Lambda[1] - Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (-Lambda[0] + 2*Lambda[1] - delta,), (Lambda[0] - 2*Lambda[1] + 2*Lambda[2] - delta,), (1/2*Lambda[0] - Lambda[1] + Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (Lambda[0],)] Here is a way to construct a picture of a Demazure crystal using the ``subset`` option:: sage: B = CrystalOfTableaux(['A',2], shape=[2,1]) sage: C = CombinatorialFreeModule(QQ,B) sage: t = B.highest_weight_vector() sage: b = C(t) sage: D = B.demazure_operator(b,[2,1]); D B[[[1, 1], [2]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 1], [3]]] + B[[[1, 3], [3]]] sage: G = B.digraph(subset=D.support()) sage: G.vertices() [[[1, 1], [2]], [[1, 2], [2]], [[1, 3], [2]], [[1, 1], [3]], [[1, 3], [3]]] sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz We can also choose to display particular arrows using the ``index_set`` option:: sage: C = KirillovReshetikhinCrystal(['D',4,1], 2, 1) sage: G = C.digraph(index_set=[1,3]) sage: len(G.edges()) 20 sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz TODO: add more tests """ from sage.graphs.all import DiGraph from sage.categories.highest_weight_crystals import HighestWeightCrystals d = {} if self in HighestWeightCrystals: f = lambda (u,v,label): ({}) else: f = lambda (u,v,label): ({"backward":label ==0}) # Parse optional arguments if subset is None: subset = self if index_set is None: index_set = self.index_set() for x in subset: d[x] = {} for i in index_set: child = x.f(i) if child is None or child not in subset: continue d[x][child]=i G = DiGraph(d) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = self.cartan_type()._index_set_coloring, edge_options = f) 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 markov_chain_digraph(self, action = 'promotion', labeling = 'identity'): r""" Returns the digraph of the action of generalized promotion or tau on ``self`` INPUT: - ``action`` -- 'promotion' or 'tau' (default: 'promotion') - ``labeling`` -- 'identity' or 'source' (default: 'identity') .. todo:: - generalize this feature by accepting a family of operators as input - move up in some appropriate category This method creates a graph with vertices being the linear extensions of a given finite poset and an edge from `\pi` to `\pi'` if `\pi' = \pi \partial_i` where `\partial_i` is the promotion operator (see :meth:`promotion`) if ``action`` is set to ``promotion`` and `\tau_i` (see :meth:`tau`) if ``action`` is set to ``tau``. The label of the edge is `i` (resp. `\pi_i`) if ``labeling`` is set to ``identity`` (resp. ``source``). EXAMPLES:: sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True) sage: L = P.linear_extensions() sage: G = L.markov_chain_digraph(); G Looped multi-digraph on 5 vertices sage: sorted(G.vertices(), key = repr) [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: sorted(G.edges(), key = repr) [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 2, 4, 3], 4), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([1, 4, 2, 3], [1, 4, 2, 3], 4), ([2, 1, 3, 4], [1, 2, 4, 3], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 2), ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 2), ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 4)] sage: G = L.markov_chain_digraph(labeling = 'source') sage: sorted(G.vertices(), key = repr) [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: sorted(G.edges(), key = repr) [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 4), ([1, 2, 4, 3], [1, 2, 4, 3], 3), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 4), ([1, 4, 2, 3], [1, 4, 2, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([2, 1, 3, 4], [1, 2, 4, 3], 2), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 1), ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 2), ([2, 1, 4, 3], [2, 1, 3, 4], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 4), ([2, 1, 4, 3], [2, 1, 4, 3], 3)] The edges of the graph are by default colored using blue for edge 1, red for edge 2, green for edge 3, and yellow for edge 4:: sage: view(G) #optional - dot2tex graphviz Alternatively, one may get the graph of the action of the ``tau`` operator:: sage: G = L.markov_chain_digraph(action='tau'); G Looped multi-digraph on 5 vertices sage: sorted(G.vertices(), key = repr) [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: sorted(G.edges(), key = repr) [([1, 2, 3, 4], [1, 2, 3, 4], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), ([1, 2, 3, 4], [2, 1, 3, 4], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 4, 3], 1), ([1, 4, 2, 3], [1, 2, 4, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 1), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([2, 1, 3, 4], [1, 2, 3, 4], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 2), ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 2, 4, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 2)] sage: view(G) #optional - dot2tex graphviz .. seealso:: :meth:`markov_chain_transition_matrix`, :meth:`promotion`, :meth:`tau` TESTS:: sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True, facade = True) sage: L = P.linear_extensions() sage: G = L.markov_chain_digraph(labeling = 'source'); G Looped multi-digraph on 5 vertices """ d = dict([x,dict([y,[]] for y in self)] for x in self) if action == 'promotion': R = range(self.poset().cardinality()) else: R = range(self.poset().cardinality()-1) if labeling == 'source': for x in self: for i in R: child = getattr(x, action)(i+1) d[x][child]+=[self.poset().unwrap(x[i])] else: for x in self: for i in R: child = getattr(x, action)(i+1) d[x][child]+=[i+1] G = DiGraph(d) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = {1:"blue", 2:"red", 3:"green", 4:"yellow"}) #G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = {1:"green", 2:"blue", 3:"brown", 4:"red"}) return G
def dual_equivalence_graph(self, X=None, index_set=None, directed=True): r""" Return the dual equivalence graph indexed by ``index_set`` on the subset ``X`` of ``self``. Let `b \in B` be an element of weight `0`, so `\varepsilon_j(b) = \varphi_j(b)` for all `j \in I`, where `I` is the indexing set. We say `b'` is an `i`-elementary dual equivalence transformation of `b` (where `i \in I`) if * `\varepsilon_i(b) = 1` and `\varepsilon_{i-1}(b) = 0`, and * `b' = f_{i-1} f_i e_{i-1} e_i b`. We can do the inverse procedure by interchanging `i` and `i-1` above. .. NOTE:: If the index set is not an ordered interval, we let `i - 1` mean the index appearing before `i` in `I`. This definition comes from [Assaf08]_ Section 4 (where our `\varphi_j(b)` and `\varepsilon_j(b)` are denoted by `\epsilon(b, j)` and `-\delta(b, j)`, respectively). The dual equivalence graph of `B` is defined to be the colored graph whose vertices are the elements of `B` of weight `0`, and whose edges of color `i` (for `i \in I`) connect pairs `\{ b, b' \}` such that `b'` is an `i`-elementary dual equivalence transformation of `b`. .. NOTE:: This dual equivalence graph is a generalization of `\mathcal{G}\left(\mathcal{X}\right)` in [Assaf08]_ Section 4 except we do not require `\varepsilon_i(b) = 0, 1` for all `i`. This definition can be generalized by choosing a subset `X` of the set of all vertices of `B` of weight `0`, and restricting the dual equivalence graph to the vertex set `X`. INPUT: - ``X`` -- (optional) the vertex set `X` (default: the whole set of vertices of ``self`` of weight `0`) - ``index_set`` -- (optional) the index set `I` (default: the whole index set of ``self``); this has to be a subset of the index set of ``self`` (as a list or tuple) - ``directed`` -- (default: ``True``) whether to have the dual equivalence graph be directed, where the head of an edge `b - b'` is `b` and the tail is `b' = f_{i-1} f_i e_{i-1} e_i b`) .. SEEALSO:: :meth:`sage.combinat.partition.Partition.dual_equivalence_graph` REFERENCES: .. [Assaf08] Sami Assaf. *A combinatorial realization of Schur-Weyl duality via crystal graphs and dual equivalence graphs*. FPSAC 2008, 141-152, Discrete Math. Theor. Comput. Sci. Proc., AJ, Assoc. Discrete Math. Theor. Comput. Sci., (2008). :arxiv:`0804.1587v1` EXAMPLES:: sage: T = crystals.Tableaux(['A',3], shape=[2,2]) sage: G = T.dual_equivalence_graph() sage: sorted(G.edges()) [([[1, 3], [2, 4]], [[1, 2], [3, 4]], 2), ([[1, 2], [3, 4]], [[1, 3], [2, 4]], 3)] sage: T = crystals.Tableaux(['A',4], shape=[3,2]) sage: G = T.dual_equivalence_graph() sage: sorted(G.edges()) [([[1, 3, 5], [2, 4]], [[1, 3, 4], [2, 5]], 4), ([[1, 3, 5], [2, 4]], [[1, 2, 5], [3, 4]], 2), ([[1, 3, 4], [2, 5]], [[1, 2, 4], [3, 5]], 2), ([[1, 2, 5], [3, 4]], [[1, 3, 5], [2, 4]], 3), ([[1, 2, 4], [3, 5]], [[1, 2, 3], [4, 5]], 3), ([[1, 2, 3], [4, 5]], [[1, 2, 4], [3, 5]], 4)] sage: T = crystals.Tableaux(['A',4], shape=[3,1]) sage: G = T.dual_equivalence_graph(index_set=[1,2,3]) sage: G.vertices() [[[1, 3, 4], [2]], [[1, 2, 4], [3]], [[1, 2, 3], [4]]] sage: G.edges() [([[1, 3, 4], [2]], [[1, 2, 4], [3]], 2), ([[1, 2, 4], [3]], [[1, 2, 3], [4]], 3)] TESTS:: sage: T = crystals.Tableaux(['A',4], shape=[3,1]) sage: G = T.dual_equivalence_graph(index_set=[2,3]) sage: sorted(G.edges()) [([[1, 2, 4], [3]], [[1, 2, 3], [4]], 3), ([[2, 4, 5], [3]], [[2, 3, 5], [4]], 3)] sage: sorted(G.vertices()) [[[1, 3, 4], [2]], [[1, 2, 4], [3]], [[2, 4, 5], [3]], [[1, 2, 3], [4]], [[2, 3, 5], [4]], [[1, 1, 1], [5]], [[1, 1, 5], [5]], [[1, 5, 5], [5]], [[2, 3, 4], [5]]] """ if index_set is None: index_set = self.index_set() def wt_zero(x): for i in index_set: if x.epsilon(i) != x.phi(i): return False return True if X is None: X = [x for x in self if wt_zero(x)] checker = lambda x: True elif any(not wt_zero(x) for x in X): raise ValueError("the elements are not all weight 0") else: checker = lambda x: x in X edges = [] for x in X: for k, i in enumerate(index_set[1:]): im = index_set[k] if x.epsilon(i) == 1 and x.epsilon(im) == 0: y = x.e(i).e(im).f(i).f(im) if checker(y): edges.append([x, y, i]) from sage.graphs.all import DiGraph G = DiGraph([X, edges], format="vertices_and_edges", immutable=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) return G
def digraph(self, subset=None, index_set=None): """ Returns the DiGraph associated to ``self``. INPUT: - ``subset`` -- (Optional) A subset of vertices for which the digraph should be constructed - ``index_set`` -- (Optional) The index set to draw arrows EXAMPLES:: sage: C = Crystals().example(5) sage: C.digraph() Digraph on 6 vertices The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, and green for edge 3:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz One may also overwrite the colors:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"}) sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz Or one may add colors to yet unspecified edges:: sage: C = Crystals().example(4) sage: G = C.digraph() sage: C.cartan_type()._index_set_coloring[4]="purple" sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz Here is an example of how to take the top part up to a given depth of an infinite dimensional crystal:: sage: C = CartanType(['C',2,1]) sage: La = C.root_system().weight_lattice().fundamental_weights() sage: T = crystals.HighestWeight(La[0]) sage: S = T.subcrystal(max_depth=3) sage: G = T.digraph(subset=S); G Digraph on 5 vertices sage: sorted(G.vertices(), key=str) [(-Lambda[0] + 2*Lambda[1] - delta,), (1/2*Lambda[0] + Lambda[1] - Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (1/2*Lambda[0] - Lambda[1] + Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (Lambda[0] - 2*Lambda[1] + 2*Lambda[2] - delta,), (Lambda[0],)] Here is a way to construct a picture of a Demazure crystal using the ``subset`` option:: sage: B = crystals.Tableaux(['A',2], shape=[2,1]) sage: C = CombinatorialFreeModule(QQ,B) sage: t = B.highest_weight_vector() sage: b = C(t) sage: D = B.demazure_operator(b,[2,1]); D B[[[1, 1], [2]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 1], [3]]] + B[[[1, 3], [3]]] sage: G = B.digraph(subset=D.support()) sage: G.vertices() [[[1, 1], [2]], [[1, 2], [2]], [[1, 3], [2]], [[1, 1], [3]], [[1, 3], [3]]] sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz We can also choose to display particular arrows using the ``index_set`` option:: sage: C = crystals.KirillovReshetikhin(['D',4,1], 2, 1) sage: G = C.digraph(index_set=[1,3]) sage: len(G.edges()) 20 sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz TODO: add more tests """ from sage.graphs.all import DiGraph from sage.categories.highest_weight_crystals import HighestWeightCrystals d = {} if self in HighestWeightCrystals: f = lambda u_v_label: ({}) else: f = lambda u_v_label: ({"backward": u_v_label[2] == 0}) # Parse optional arguments if subset is None: subset = self if index_set is None: index_set = self.index_set() for x in subset: d[x] = {} for i in index_set: child = x.f(i) if child is None or child not in subset: continue d[x][child]=i G = DiGraph(d) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = self.cartan_type()._index_set_coloring, edge_options = f) return G