def _build_core(self): """ Builds the core. Called when Core is initialized. """ ends={} # remove signs from ends for x in self._signed_ends.keys(): ends[x]=[] for e in self._signed_ends[x]: ends[x].append(e[1:] if e[0]=='-' else e) inv_alph=self._inv_graph_map.domain()._alphabet for x in self._graph_map.domain().edge_labels(): slice_x=GraphWithInverses(alphabet=inv_alph) # find common prefix if ends[x]: common=ends[x][0] for e in ends[x]: common_len=self._inv_graph_map.domain().common_prefix_length(e,common) common=common[:common_len] # build slice for e in ends[x]: v_label=common for a in e[common_len:-1]: if v_label not in slice_x.vertices(): slice_x.add_vertex(v_label) t_label=v_label+a if t_label not in slice_x.vertices(): slice_x.add_vertex(t_label) edge=(v_label,t_label,a) if edge not in slice_x.edges(): slice_x.add_edge(edge) v_label=t_label self._core_slice[x]=slice_x
def HNN_splitting(A): """ The marked metric graph corresponding to the HNN splitting F_N=F_{N-1}*<t>. This is rose marked graph with all edges of length 0 except ``A[0]`` which is of length 1. INPUT: - ``A`` alphabet OUTPUT: The marked metric graph corresponding to the HNN splitting EXAMPLES:: sage: A=AlphabetWithInverses(4) sage: print MarkedMetricGraph.HNN_splitting(A) Marked graph: a: 0->0, b: 0->0, c: 0->0, d: 0->0 Marking: a->a, b->b, c->c, d->d Length: a:1, b:0, c:0, d:0 """ length = dict((a, 0) for a in A.positive_letters()) length[A[0]] = 1 RA = GraphWithInverses.rose_graph(A) RAA = GraphWithInverses.rose_graph(A) marking = GraphMap(RA, RAA, dict( (a, Word([a])) for a in A.positive_letters())) return MarkedMetricGraph(marking=marking, length=length)
def blow_up_vertices(self, germ_components): """ Blow-up ``self`` according to classes of germs given in ``germ_components``. INPUT: - ``germ_components`` a list of classes of germs outgoing from a vertex. OUTPUT: A dictionary that maps an old edge to the path in the new graph. EXAMPLES:: sage: G = MarkedGraph.rose_marked_graph(AlphabetWithInverses(2)) sage: G.blow_up_vertices([['a','A'],['b'],['B']]) {'A': word: cAC, 'B': word: eBD, 'a': word: caC, 'b': word: dbE} sage: print G Marked graph: a: 1->1, b: 2->3, c: 0->1, d: 0->2, e: 0->3 Marking: a->caC, b->dbE """ blow_up_map = GraphWithInverses.blow_up_vertices(self, germ_components) blow_up_morph = WordMorphism(blow_up_map) self._marking.set_edge_map(blow_up_morph * self.marking().edge_map()) return blow_up_map
def fold(self,edges_full,edges_partial): """ Folds the list of edges. Some edges are fully folded and some are only partially folded. All edges are assumed to start form the same vertex. Edges are given by their label. In the terminology of Stallings folds the partially fold edges are subdivided and then fold. The first element of ``edges_full`` is allowed to be a tuple ``(path,'path')`` and not an ``edge_label``. Then the other edges will be folded to the whole ``path``. In Stallings terminology, this is a sequence of folds of the successive edges of ``path``. INPUT: ``edges_full``, ``edges_partial`` are list of edges (each possibly empty, but the union must have at least two edges). OUTPUT: A dictionnary that maps old edges to new graph paths. SEE ALSO: ``GraphWithInverses.fold()`` """ fold_map=GraphWithInverses.fold(self,edges_full,edges_partial) fold_morph=WordMorphism(fold_map) self._marking.set_edge_map(fold_morph*self._marking._edge_map) return fold_map
def contract_forest(self, forest): """ Contract the forest. Each tree of the forest is contracted to the initial vertex of its first edge. INPUT: - ``forest`` is a list of disjoint subtrees each given as lists of edges. OUTPUT: A dictionary that maps old edges to new edges. SEE ALSO: ``GraphWithInverses.contract_forest()`` """ contract_map = GraphWithInverses.contract_forest(self, forest) contract_morph = WordMorphism(contract_map) self._marking.set_edge_map(contract_morph * self._marking._edge_map) return contract_map
def rose_marked_graph(alphabet): """ The rose on ``alphabet`` marked with the identity. """ marking=dict((a,a) for a in alphabet.positive_letters()) return MarkedGraph(graph=GraphWithInverses.rose_graph(alphabet),marking=marking,marking_alphabet=alphabet)
def HNN_splitting(A): """ The marked metric graph corresponding to the HNN splitting F_N=F_{N-1}*<t>. The rose marked graph with all edges of length 0 except ``A[0]`` which is of length 1. """ length=dict((a,0) for a in A.positive_letters()) length[A[0]]=1 RA=GraphWithInverses.rose_graph(A) RAA=GraphWithInverses.rose_graph(A) marking=GraphMap(RA,RAA,edge_map=dict((a,Word([a])) for a in A.positive_letters())) return MarkedMetricGraph(marking=marking,length=length)
def rose_map(automorphism): """ Returns the core of the rose and the rose acted upon by the corresponding automorphism. """ graph=GraphWithInverses.rose_graph(automorphism._domain._alphabet.copy()) inv_automorphism=automorphism.inverse() return Core(graph,graph,automorphism,inv_automorphism)
def rose_map(automorphism): """ Returns the core of the rose and the rose acted upon by the corresponding automorphism. """ graph = GraphWithInverses.rose_graph( automorphism._domain._alphabet.copy()) inv_automorphism = automorphism.inverse() return Core(graph, graph, automorphism, inv_automorphism)
def fold(self, edges_full, edges_partial): """ Folds the list of edges. Some edges are fully folded and some are only partially folded. All edges are assumed to start form the same vertex. Edges are given by their label. In the terminology of Stallings folds the partially fold edges are subdivided and then fold. The first element of ``edges_full`` is allowed to be a tuple ``(path,'path')`` and not an ``edge_label``. Then the other edges will be folded to the whole ``path``. In Stallings terminology, this is a sequence of folds of the successive edges of ``path``. INPUT: - ``edges_full``, are list of edges - ``edges_partial`` are list of edges (each possibly empty, but the union must have at least two edges). OUTPUT: A dictionary that maps old edges to new graph paths. EXAMPLES:: sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']]) sage: G = MarkedGraph(G) sage: G.fold(['b'],['a']) {'A': word: AB, 'B': word: B, 'C': word: C, 'a': word: ba, 'b': word: b, 'c': word: c} sage: print G Marked graph: a: 1->0, b: 0->1, c: 1->1 Marking: a->ba, b->bcB SEE ALSO: ``GraphWithInverses.fold()`` """ fold_map = GraphWithInverses.fold(self, edges_full, edges_partial) fold_morph = WordMorphism(fold_map) self._marking.set_edge_map(fold_morph * self._marking._edge_map) return fold_map
def rose_conjugacy_representative(self): """ Topological representative of the conjugacy class of ``self``. SEE ALSO: This is the same as ``self.rose_representative()`` but the base graph of the ``TopologicalRepresentative`` is a ``GraphWithInverses`` instead of a ``MarkedGraph``. """ from topological_representative import TopologicalRepresentative from inverse_graph import GraphWithInverses return TopologicalRepresentative(GraphWithInverses.rose_graph(self._domain.alphabet()),self)
def rose_conjugacy_representative(self): """ Topological representative of the conjugacy class of ``self``. SEE ALSO: This is the same as ``self.rose_representative()`` but the base graph of the ``TopologicalRepresentative`` is a ``GraphWithInverses`` instead of a ``MarkedGraph``. """ from topological_representative import TopologicalRepresentative from inverse_graph import GraphWithInverses return TopologicalRepresentative( GraphWithInverses.rose_graph(self._domain.alphabet()), self)
def fold(self,edges_full,edges_partial): """ Folds the edges. OUTPUT: A dictionnary that maps old edges to new graph paths. SEE ALSO: ``GraphWithInverses.fold()`` """ fold_map=GraphWithInverses.fold(self,edges_full,edges_partial) fold_morph=WordMorphism(fold_map) self._marking.set_edge_map(fold_morph*self._marking._edge_map) return fold_map
def subdivide(self,edge_list): """ Subdivides edges in the edge_list into two edges. WARNING: each edge in ``edge_list`` must appear only once. SEE ALSO: ``GraphWithInverses.subdivide()`` """ subdivide_map=GraphWithInverses.subdivide(self,edge_list) subdivide_morph=WordMorphism(subdivide_map) self._marking.set_edge_map(subdivide_morph*self._marking._edge_map) return subdivide_map
def contract_forest(self,forest): """ Contract the forest. OUTPUT: A dictionnary that maps old edges to new edges. SEE ALSO: ``GraphWithInverses.contract_forest()`` """ contract_map=GraphWithInverses.contract_forest(self,forest) contract_morph=WordMorphism(contract_map) self._marking.set_edge_map(contract_morph*self._marking._edge_map) return contract_map
def rose_marked_graph(alphabet): """ The rose on ``alphabet`` marked with the identity. INPUT: - ``alphabet`` a alphabet OUTPUT: The rose on ``alphabet`` marked with the identity. EXAMPLES:: sage: print MarkedGraph.rose_marked_graph(AlphabetWithInverses(2)) Marked graph: a: 0->0, b: 0->0 Marking: a->a, b->b """ marking = dict((a, Word([a])) for a in alphabet.positive_letters()) return MarkedGraph(graph=GraphWithInverses.rose_graph(alphabet), marking=marking, marking_alphabet=alphabet)
def subdivide(self, edge_list): """ Subdivides each edge in the edge_list into two edges. INPUT: - ``edge_list`` edge list OUTPUT: subdivide map from subdivide GraphWithInverses WARNING: each edge in ``edge_list`` must appear only once. EXAMPLES:: sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']]) sage: G = MarkedGraph(G) sage: G.subdivide(['a','c']) {'A': word: DA, 'B': word: B, 'C': word: EC, 'a': word: ad, 'b': word: b, 'c': word: ce} sage: print G Marked graph: a: 0->2, b: 0->1, c: 1->3, d: 2->0, e: 3->1 Marking: a->ad, b->bceB SEE ALSO:: GraphWithInverses.subdivide() """ subdivide_map = GraphWithInverses.subdivide(self, edge_list) subdivide_morph = WordMorphism(subdivide_map) self._marking.set_edge_map(subdivide_morph * self._marking._edge_map) return subdivide_map
def rose_map(automorphism): """ The graph map of the rose representing the automorphism. The rose is built on a copy of the alphabet of the domain of ``automorphism``. OUTPUT: The graph map of the rose representing the automorphism. EXAMPLES:: sage: phi=FreeGroupAutomorphism('a->ab,b->ac,c->a') sage: print GraphMap.rose_map(phi) Graph map: Graph with inverses: a: 0->0, b: 0->0, c: 0->0 Graph with inverses: a: 0->0, b: 0->0, c: 0->0 edge map: a->ab, b->ac, c->a """ graph = GraphWithInverses.rose_graph( automorphism.domain().alphabet().copy()) return GraphMap(graph, graph, automorphism)
def __init__(self,graph=None,marking=None,alphabet=None,marking_alphabet=None): if isinstance(marking,GraphMap): GraphWithInverses.__init__(self,marking.codomain(),marking.codomain().alphabet()) self._marking=marking else: if isinstance(graph,GraphWithInverses): alphabet=graph._alphabet GraphWithInverses.__init__(self,graph,alphabet) if marking is None: #computes a (random) marking from a rose equivalent to graph A=graph.alphabet() tree=graph.spanning_tree() j=0 letter=dict() for a in A.positive_letters(): vi=graph.initial_vertex(a) vt=graph.terminal_vertex(a) if (len(tree[vi])==0 or tree[vi][-1]!=A.inverse_letter(a)) and\ (len(tree[vt])==0 or tree[vt][-1]!=a): letter[j]=a j=j+1 B=AlphabetWithInverses(j) RB=GraphWithInverses.rose_graph(B) edge_map=dict() for i in xrange(j): a=letter[i] edge_map[B[i]]=graph.reduce_path(tree[graph.initial_vertex(a)]\ *Word([a])\ *graph.reverse_path(tree[graph.terminal_vertex(a)])) marking=GraphMap(RB,graph,edge_map) else: marking=GraphMap(GraphWithInverses.rose_graph(marking_alphabet),self,marking) self._marking=marking
def __init__(self, graph=None, marking=None, alphabet=None, marking_alphabet=None): """ INPUT: - ``graph`` -- (default None) GraphWithInverses is expected or will be combute from ''grap'' - ``marking`` -- (default None) GraphMap is expected or will be compute - ``alphabet`` -- (default None) if ``graph`` is GraphWithInverses ``alphabet`` will be use for ``self`` - ``marking_alphabet`` -- (default None) alphabet used in the case of a ``MarkedGraph`` is created from a GraphWithInverses`` by computing (randomly) a rose equivalent to the graph. EXAMPLES:: sage: G = GraphWithInverses({'a':(0,0),'b':(0,1),'c':(1,0)}) sage: M = MarkedGraph(graph=G) sage: print M Marked graph: a: 0->0, c: 1->0, b: 0->1 Marking: a->a, b->bc sage: A = AlphabetWithInverses(2) sage: G = GraphWithInverses.rose_graph(A) sage: H = GraphWithInverses.rose_graph(A) sage: f = GraphMap(G,H,"a->aba,b->ab") sage: M = MarkedGraph(marking=f) sage: print M Marked graph: a: 0->0, b: 0->0 Marking: a->aba, b->ab sage: print MarkedGraph(marking=f, alphabet=A) Marked graph: a: 0->0, b: 0->0 Marking: a->aba, b->ab sage: print MarkedGraph(marking=f, marking_alphabet=A) Marked graph: a: 0->0, b: 0->0 Marking: a->aba, b->ab """ if isinstance(marking, GraphMap): GraphWithInverses.__init__(self, marking.codomain(), marking.codomain().alphabet()) self._marking = marking else: if isinstance(graph, GraphWithInverses): alphabet = graph.alphabet() GraphWithInverses.__init__(self, graph, alphabet) if marking is None: # computes a (random) marking # from a rose equivalent to graph A = graph.alphabet() tree = graph.spanning_tree() j = 0 letter = dict() for a in A.positive_letters(): vi = graph.initial_vertex(a) vt = graph.terminal_vertex(a) if (len(tree[vi]) == 0 or tree[vi][-1] != A.inverse_letter(a)) \ and (len(tree[vt]) == 0 or tree[vt][-1] != a): letter[j] = a j = j + 1 B = AlphabetWithInverses(j) RB = GraphWithInverses.rose_graph(B) edge_map = dict() for i in xrange(j): a = letter[i] edge_map[B[i]] = graph.reduce_path( tree[graph.initial_vertex(a)] * Word([a]) * graph.reverse_path(tree[graph.terminal_vertex(a)])) marking = GraphMap(RB, graph, edge_map) else: marking = GraphMap( GraphWithInverses.rose_graph(marking_alphabet), self, marking) self._marking = marking
def _build_core(self): """ Builds the core. Called when Core is initialized. """ ends = {} # remove signs from ends for x in self._signed_ends.keys(): ends[x] = [] for e in self._signed_ends[x]: ends[x].append(e[1:] if e[0] == '-' else e) inv_alph = self._inv_graph_map.domain()._alphabet for x in self._graph_map.domain().edge_labels(): slice_x = GraphWithInverses(alphabet=inv_alph) # find common prefix if ends[x]: common = ends[x][0] for e in ends[x]: common_len = self._inv_graph_map.domain().common_prefix_length( e, common) common = common[:common_len] # build slice for e in ends[x]: v_label = common for a in e[common_len:-1]: if v_label not in slice_x.vertices(): slice_x.add_vertex(v_label) t_label = v_label + a if t_label not in slice_x.vertices(): slice_x.add_vertex(t_label) edge = (v_label, t_label, a) if edge not in slice_x.edges(): slice_x.add_edge(edge) v_label = t_label self._core_slice[x] = slice_x
def pullback(self, other): r""" Pullback of the graph maps ``self`` and ``other``. The codomain of ``self`` and ``other`` must be the same graph. The pullback is a graph map f:G3 -> G that makes the diagram commute: G3 -----> G1 | \ | | \ | self | \f | | \ | V _\| V G2 -----> G ....other The pullback method can be used to find intersection of two subgroups of a Free Group. INPUT: - ``other``: a graph map G2->G with ``self``: a graph map G1->G, OUTPUT: A ``GraphMap`` f EXAMPLES:: sage: G1 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='x0')) sage: G2 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='a0')) sage: G = GraphWithInverses.rose_graph(AlphabetWithInverses(2)) sage: n1 = WordMorphism({'x0':['a','a'],'x1':['b','a']}) sage: n2 = WordMorphism({'a0':['b','a'],'a1':['b','b','b','a','B','a']}) sage: f1 = GraphMap(G1,G,n1) sage: f2 = GraphMap(G2,G,n2) sage: print f1.pullback(f2) Graph map: Graph with inverses: a0: (0, 0)->(1, 2), a1: (0, 2)->(1, 0), a2: (0, 2)->(1, 3), a3: (0, 3)->(1, 4), a4: (0, 4)->(1, 3), a5: (1, 2)->(0, 0), a6: (1, 4)->(0, 3) Graph with inverses: a: 0->0, b: 0->0 edge map: a0->b, a1->a, a2->b, a3->b, a4->a, a5->a, a6->a AUTHORS: - Radhika GUPTA """ import itertools # First convert self and f2 into immersions self.stallings_folding() other.stallings_folding() G3 = GraphWithInverses() A = AlphabetWithInverses(0, type='a0') d = {} # get set of vertices V = [] for i in itertools.product(self.domain().vertices(), other.domain().vertices()): V.append(i) # add edges for v in V: for w in V: for e1 in self.domain().alphabet().positive_letters(): if self.domain().initial_vertex(e1) == v[0] \ and self.domain().terminal_vertex(e1) == w[0]: for e2 in other.domain().alphabet().positive_letters(): if other.domain().initial_vertex(e2) == v[1] \ and other.domain().terminal_vertex(e2) == w[ 1]: if self.image(e1) == other.image(e2): e = A.add_new_letter() G3.add_edge(v, w, e) # update dictionary to define map on G3 d[e[0]] = self.image(e1) G3._alphabet = A n3 = WordMorphism(d) G = self.codomain() # same as other.codomain() return GraphMap(G3, G, n3)