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 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 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 _digraph(self): r""" Constructs the underlying digraph and stores the result as an attribute. EXAMPLES:: sage: from sage.combinat.yang_baxter_graph import SwapIncreasingOperator sage: ops = [SwapIncreasingOperator(i) for i in range(2)] sage: Y = YangBaxterGraph(root=(1,2,3), operators=ops) sage: Y._digraph Digraph on 6 vertices """ digraph = DiGraph() digraph.add_vertex(self._root) queue = [self._root] while queue: u = queue.pop() for (v, l) in self._succesors(u): if v not in digraph: queue.append(v) digraph.add_edge(u, v, l) return digraph
def _digraph_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 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)
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)