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 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_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 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(edges) G.add_vertices(X) if have_dot2tex(): G.set_latex_options( format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring ) 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(edges) G.add_vertices(X) 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)
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)