Ejemplo n.º 1
0
    def __init__(self, data, group=None):
        """
        Builds a FreeGroupAutomorphism from data.

        INPUT:

        - ``data`` - the data used to build the morphism

        - ``group`` - an optional free group
        """
        if group is not None and not isinstance(group, FreeGroup):
            raise ValueError, "the group must be a Free Group"

        WordMorphism.__init__(self, data)

        if group is None:
            A = AlphabetWithInverses(self.domain().alphabet())
            F = FreeGroup(A)
        else:
            F = group
            A = group.alphabet()

        self._domain = F
        self._codomain = F  # unuseful... consistency with WordMorphism
        for letter in self._morph.keys():
            self._morph[letter] = F.reduce(self._morph[letter])
            self._morph[A.inverse_letter(letter)] = F.inverse_word(
                self._morph[letter])
Ejemplo n.º 2
0
    def to_word_morphism(self, forget_inverse=False):
        r"""
        Return a word morphism.

        .. NOTE::

            This method should not be there but on the other hand,
            f.periodic_points() fails for FreeGroupMorphism and
            FreeGroupAutomorphism

        EXAMPLES::

            sage: f = FreeGroupAutomorphism('a->AD,b->Adac,c->bd,d->c')
            sage: f.to_word_morphism().periodic_points()
            [[word: AdacccADDBdacADbdbdbddaCCCADacADbddaCAda...,
              word: dacADbdbdbddaCCCADacADbddaCAdaccAdaccAda...,
              word: cADbddaCAdaccAdaccAdacccADDBDBDBdaCADbdd...,
              word: bddaCAdacccADDBdacADbdbddacADbdbddacADbd...],
             [word: CCADaCCADacADDBdaCCCADaCCADacADDBdaCAdac...,
              word: DBDBdaCADDBDBdaCADbddaCCCADacADDBDBDBdaC...]]
        """
        if forget_inverse:
            A = self.domain().alphabet()
            f = {}
            for a in A.positive_letters():
                f[a] = map(A.to_positive_letter, self.image(a))
            return WordMorphism(f)

        return WordMorphism(
            dict((a, list(self.image(a))) for a in self.domain().alphabet()))
    def __init__(self,data,group=None):
        """
        Builds a FreeGroupAutomorphism from data.

        INPUT:

        - ``data`` - the data used to build the morphism

        - ``group`` - an optional free group
        """
        if group is not None and not isinstance(group, FreeGroup):
            raise ValueError, "the group must be a Free Group"

        WordMorphism.__init__(self,data)

        if group is None:
            A = AlphabetWithInverses(self.domain().alphabet())
            F = FreeGroup(A)
        else:
            F = group
            A = group.alphabet()

        self._domain = F
        self._codomain = F  # unuseful... consistency with WordMorphism
        for letter in self._morph.keys():
            self._morph[letter]=F.reduce(self._morph[letter])
            self._morph[A.inverse_letter(letter)] = F.inverse_word(self._morph[letter])
Ejemplo n.º 4
0
 def __init__(self, sigma, k, presuf='prefix', dual=False):
     self._sigma_dict = sigma
     self._sigma = WordMorphism(sigma)
     self._k = k
     if not presuf in ['prefix', 'suffix']:
         raise ValueError('Input presuf(={}) should be "prefix" or'
                          ' "suffix"'.format(presuf))
     self._presuf = presuf
     self._dual = dual
Ejemplo n.º 5
0
    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: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.marked_graph import MarkedGraph
            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
Ejemplo n.º 6
0
    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.


        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.marked_graph import MarkedGraph
            sage: G = MarkedGraph.rose_marked_graph(AlphabetWithInverses(2))
            sage: G.contract_forest([['b']])
            {'A': word: A, 'B': word: , 'a': word: a, 'b': word: }

        .. SEEALSO::

            :meth:`train_track.inverse_graph.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
Ejemplo n.º 7
0
    def identity_morphism(self):
        r"""
        Returns the identity morphism from self to itself.

        EXAMPLES::

            sage: W = Words('ab')
            sage: W.identity_morphism()
            WordMorphism: a->a, b->b

        ::

            sage: W = Words(range(3))
            sage: W.identity_morphism()
            WordMorphism: 0->0, 1->1, 2->2

        There is no support yet for infinite alphabet::

            sage: W = Words(alphabet=Alphabet(name='NN'))
            sage: W
            Words over Non negative integers
            sage: W.identity_morphism()
            Traceback (most recent call last):
            ...
            NotImplementedError: size of alphabet must be finite
        """
        if self.size_of_alphabet() not in ZZ:
            raise NotImplementedError, 'size of alphabet must be finite'
        from sage.combinat.words.morphism import WordMorphism
        return WordMorphism(dict((a, a) for a in self.alphabet()))
Ejemplo n.º 8
0
 def _int_list_to_substitutions(self, words):
     A = self._start._alphabet
     W = FiniteWords(A)
     s = {}
     for i, w in enumerate(words):
         s[A.unrank(i)] = [A.unrank(j) for j in w]
     return WordMorphism(s, domain=W, codomain=W)
Ejemplo n.º 9
0
    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``. 

        The domain of ``self`` is fold until we get an immersion. 

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.  

        Then fold one gate at one vertex and update the edge map and
        illegal turns list. 

        Repeat the process till no illegal turns remain.

        REFERENCES:

        [Stallings] J. Stallings, Topology of Finite Graphs,
        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) > 1:
                self.subdivide_domain(a)

        Turns = self._domain.turns(
        )  #list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(
            Turns)  # list of illegal turns in domain after subdivision
        counter = 0
        while len(Il_turns) > 0:
            counter = counter + 1

            # find edge_list (list of edges in the gate correspoding to e1) to fold at exactly one vertex
            e1 = Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(
                        a) == self._domain.initial_vertex(e1) and e1 != a:
                    if (e1, a) in Il_turns or (a, e1) in Il_turns:
                        edge_list.append(a)

            edge_list = list(set(edge_list))  # remove duplicates
            # fold at initial_vertex of e1 ( this function updates the domain and edge_map)
            self._domain.fold(edge_list, [])

            #update edge_map again
            d = {}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)
            Turns = self._domain.turns()  #update list of all turns in domain
            Il_turns = self.illegal_turns(
                Turns)  # update list of illegal turns in domain

        return self
Ejemplo n.º 10
0
def iter_conjugate_classP(words, n):
    r"""
    EXAMPLES::

        sage: from slabbe.word_morphisms import iter_conjugate_classP
        sage: F = FiniteWords('ab')
        sage: list(iter_conjugate_classP(F, 2))
        [WordMorphism: a->a, b->a,
         WordMorphism: a->a, b->b,
         WordMorphism: a->b, b->a,
         WordMorphism: a->b, b->b]
        sage: list(iter_conjugate_classP(F, 3))
        [WordMorphism: a->aa, b->a,
         WordMorphism: a->aa, b->b,
         WordMorphism: a->bb, b->a,
         WordMorphism: a->bb, b->b,
         WordMorphism: a->a, b->aa,
         WordMorphism: a->a, b->bb,
         WordMorphism: a->b, b->aa,
         WordMorphism: a->b, b->bb,
         WordMorphism: a->ba, b->b,
         WordMorphism: a->ab, b->a,
         WordMorphism: a->b, b->ba,
         WordMorphism: a->a, b->ab]
    """
    alphabet = words.alphabet()
    length = alphabet.cardinality()
    # images are palindromes
    for sizes in IntegerListsLex(n=n, length=length, min_part=1):
        L = [iter_palindromes(words, size) for size in sizes]
        for pals in itertools.product(*L):
            d = dict(itertools.izip(alphabet, pals))
            yield WordMorphism(d, codomain=words)
    # images are one common letter + palindrome
    for sizes in IntegerListsLex(n=n - length, length=length, min_part=0):
        L = [iter_palindromes(words, size) for size in sizes]
        for pals in itertools.product(*L):
            for b in alphabet:
                d = {
                    a: words([b]) * p
                    for a, p in itertools.izip(alphabet, pals)
                }
                if all(w.is_palindrome() for w in d.values()):
                    # already yielded above
                    continue
                yield WordMorphism(d, codomain=words)
 def __mul__(self, other):
     """
     Returns the composition self*other.
     """
     if isinstance(other,FreeGroupAutomorphism):
         m = dict((a,self(other.image(a))) for a in other.domain().alphabet().positive_letters())
         return FreeGroupAutomorphism(m,self.domain())
     else:
         return WordMorphism.__mul__(self,other)
Ejemplo n.º 12
0
    def pullback(self, f2, G3, A):
        """
        INPUT : Two Graph maps f1:G1->G, f2:G2->G, an empty GraphWithInverses G3 and an empty AlphabetWithInverses A
        OPERATION : Find the pullback G3 and a graph map f:G3->G
        OUTPUT : Graphmap f 
        
        The pullback method can be used to find intersection of two subgroups of a Free Group. 
        
        Example : 
        G1 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='x0')) 
        G2 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='a0')) 
        G =  GraphWithInverses.rose_graph(AlphabetWithInverses(2)) 
        n1 = WordMorphism({'x0':['a','a'],'x1':['b','a']})
        n2 = WordMorphism({'a0':['b','a'],'a1':['b','b','b','a','B','a']})
        f1 = GraphMap(G1,G,n1)
        f2 = GraphMap(G2,G,n2)
        G3 = GraphWithInverses()
        A = AlphabetWithInverses(0,type='a0')
        
        f1.pullback(f2,G3,A)
        """
        import itertools
        #First convert self and f2 into immersions
        self.stallings_folding()
        f2.stallings_folding()

        # G3 = GraphWithInverses()
        #A = AlphabetWithInverses(0,type='a0')
        d = {}
        #get set of vertices
        V = []
        for i in itertools.product(self.domain().vertices(),
                                   f2.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 f2.domain().alphabet().positive_letters():
                            if f2.domain().initial_vertex(
                                    e2) == v[1] and f2.domain(
                                    ).terminal_vertex(e2) == w[1]:
                                if self.image(e1) == f2.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 f2.codomain()

        return GraphMap(G3, G, n3)
Ejemplo n.º 13
0
    def set_edge_map(self,edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``Wordmorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        """
        A=self.domain().alphabet()
        tmp_map=WordMorphism(edge_map)
        m={}
        for a in tmp_map._domain.alphabet():
            m[a]=self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)]=self._codomain.reverse_path(m[a])
        self._edge_map=WordMorphism(m)
        self._vertex_map=None
Ejemplo n.º 14
0
    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``Wordmorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None
Ejemplo n.º 15
0
    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: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.marked_graph import MarkedGraph
            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

        .. SEEALSO::

            :meth:`train_track.inverse_graph.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
Ejemplo n.º 16
0
    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``WordMorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        INPUT:

        - ``edge_map`` -- anything which is accepted by ``WordMorphism(edge_map)``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.set_edge_map('a->b,b->,c->b')
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->b, b->, c->b
        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None
Ejemplo n.º 17
0
    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``WordMorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        INPUT:

        - ``edge_map`` -- anything which is accepted by ``WordMorphism(edge_map)``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.set_edge_map('a->b,b->,c->b')
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->b, b->, c->b
        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None
Ejemplo n.º 18
0
def compute_xsi(self, u):
    r"""
    EXAMPLES::

        sage: from slabbe.word_morphisms import compute_xsi
        sage: s = WordMorphism({0:[0,1],1:[1,0]})
        sage: compute_xsi(s, Word([0]))
        sigma_u= 0->012, 1->02, 2->1
        theta_u= 0->011, 1->01, 2->0
        psi= 0->(0, 0),(0, 1),(0, 2), 1->(1, 0),(1, 1), 2->(2, 0)
        psi*sigma_u= 0->(0, 0),(0, 1),(0, 2),(1, 0),(1, 1),(2, 0), 1->(0, 0),(0, 1),(0, 2),(2, 0), 2->(1, 0),(1, 1)
        Finite words over {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (2, 0)}
        [1 0 0]
        [1 0 0]
        [1 0 0]
        [0 1 0]
        [0 1 0]
        [0 0 1]
        We want zeta such that:
        zeta((0, 0),(0, 1),(0, 2)) = (0, 0),(0, 1),(0, 2),(1, 0),(1, 1),(2, 0)
        zeta((1, 0),(1, 1)) = (0, 0),(0, 1),(0, 2),(2, 0)
        zeta((2, 0)) = (1, 0),(1, 1)
    """
    sigma_u, theta_u = return_substitution(self, u, coding=True)
    assert theta_u*sigma_u == self*theta_u, "identity is not verified"
    print("sigma_u=", sigma_u)
    print("theta_u=", theta_u)
    d = {k:[(k,i) for i in range(len(v))] for k,v in theta_u._morph.iteritems()}
    psi = WordMorphism(d)
    print("psi=", psi)
    print("psi*sigma_u=", psi*sigma_u)
    print(psi.codomain())
    print(psi.incidence_matrix())
    print("We want zeta such that:")
    for k,v in psi._morph.iteritems():
        print("zeta({}) = {}".format(v, psi(sigma_u(k))))
Ejemplo n.º 19
0
    def __mul__(self, other):
        """
        Returns the composition self*other.
        """
        if isinstance(other, FreeGroupMorphism):
            m = dict((a, self(other.image(a)))
                     for a in other.domain().alphabet().positive_letters())

            if isinstance(other,
                          FreeGroupAutomorphism) or other.is_invertible():
                return FreeGroupAutomorphism(m, self.domain())

            return FreeGroupMorphism(m, self.domain())

        return WordMorphism.__mul__(self, other)
Ejemplo n.º 20
0
    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: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.marked_graph import MarkedGraph
            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

        .. SEEALSO::

            :meth:`train_track.inverse_graph.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
Ejemplo n.º 21
0
    def rauzy_move_relabel(self, winner, side='right'):
        r"""
        Returns the relabelization obtained from this move.

        EXAMPLE::

            sage: from surface_dynamics import *

            sage: p = iet.Permutation('a b c d','d c b a')
            sage: q = p.reduced()
            sage: p_t = p.rauzy_move('t')
            sage: q_t = q.rauzy_move('t')
            sage: s_t = q.rauzy_move_relabel('t')
            sage: print(s_t)
            a->a, b->b, c->c, d->d
            sage: list(map(s_t, p_t[0])) == list(map(Word, q_t[0]))
            True
            sage: list(map(s_t, p_t[1])) == list(map(Word, q_t[1]))
            True
            sage: p_b = p.rauzy_move('b')
            sage: q_b = q.rauzy_move('b')
            sage: s_b = q.rauzy_move_relabel('b')
            sage: print(s_b)
            a->a, b->d, c->b, d->c
            sage: list(map(s_b, q_b[0])) == list(map(Word, p_b[0]))
            True
            sage: list(map(s_b, q_b[1])) == list(map(Word, p_b[1]))
            True
        """
        from surface_dynamics.interval_exchanges.labelled import LabelledPermutationIET
        from sage.combinat.words.morphism import WordMorphism

        winner = interval_conversion(winner)
        side = side_conversion(side)

        p = LabelledPermutationIET(self.list())

        l0_q = p.rauzy_move(winner, side).list()[0]

        d = dict([(self._alphabet[i],l0_q[i]) for i in range(len(self))])

        return WordMorphism(d)
Ejemplo n.º 22
0
def compute_xsi(self, u):
    r"""
    EXAMPLES::

        sage: from slabbe.word_morphisms import compute_xsi
        sage: s = WordMorphism({0:[0,1],1:[1,0]})
        sage: compute_xsi(s, Word([0]))
        sigma_u= 0->012, 1->02, 2->1
        theta_u= 0->011, 1->01, 2->0
        psi= 0->(0, 0),(0, 1),(0, 2), 1->(1, 0),(1, 1), 2->(2, 0)
        psi*sigma_u= 0->(0, 0),(0, 1),(0, 2),(1, 0),(1, 1),(2, 0), 1->(0, 0),(0, 1),(0, 2),(2, 0), 2->(1, 0),(1, 1)
        Finite words over {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (2, 0)}
        [1 0 0]
        [1 0 0]
        [1 0 0]
        [0 1 0]
        [0 1 0]
        [0 0 1]
        We want zeta such that:
        zeta((0, 0),(0, 1),(0, 2)) = (0, 0),(0, 1),(0, 2),(1, 0),(1, 1),(2, 0)
        zeta((1, 0),(1, 1)) = (0, 0),(0, 1),(0, 2),(2, 0)
        zeta((2, 0)) = (1, 0),(1, 1)
    """
    sigma_u, theta_u = return_substitution(self, u, coding=True)
    assert theta_u * sigma_u == self * theta_u, "identity is not verified"
    print "sigma_u=", sigma_u
    print "theta_u=", theta_u
    d = {
        k: [(k, i) for i in range(len(v))]
        for k, v in theta_u._morph.iteritems()
    }
    psi = WordMorphism(d)
    print "psi=", psi
    print "psi*sigma_u=", psi * sigma_u
    print psi.codomain()
    print psi.incidence_matrix()
    print "We want zeta such that:"
    for k, v in psi._morph.iteritems():
        print "zeta({}) = {}".format(v, psi(sigma_u(k)))
Ejemplo n.º 23
0
def return_substitution(self, u, coding=False, length=1000):
    r"""
    Return the return substitution of self according to factor u.

    INPUT:

    - ``self`` -- word morphism
    - ``u`` -- word such that u is a prefix of self(u)
    - ``coding`` -- boolean (default: ``False``), whether to
      include the return word coding morphism
    - ``length`` -- integer (default: ``1000``), compute the first 1000 letters
      of the derived sequence to make sure every return word are seen

    EXAMPLES::

        sage: from slabbe.word_morphisms import return_substitution
        sage: s = WordMorphism({0:[0,1],1:[1,0]})
        sage: return_substitution(s, Word([0]))
        WordMorphism: 0->012, 1->02, 2->1
        sage: return_substitution(s, Word([0,1]))
        WordMorphism: 0->01, 1->23, 2->013, 3->2
        sage: return_substitution(s, Word([0,1,1]))
        WordMorphism: 0->01, 1->23, 2->013, 3->2

    ::

        sage: return_substitution(s, Word([0]), True)
        (WordMorphism: 0->012, 1->02, 2->1, 
         WordMorphism: 0->011, 1->01, 2->0)
        sage: return_substitution(s, Word([0,1]), True)
        (WordMorphism: 0->01, 1->23, 2->013, 3->2,
         WordMorphism: 0->011, 1->010, 2->0110, 3->01)

    ::

        sage: s = WordMorphism({0:[0,0,1],1:[0,1]})
        sage: return_substitution(s, Word([0]))
        WordMorphism: 0->01, 1->011

    TESTS::

        sage: s = WordMorphism({0:[0,1],1:[1,0]})
        sage: sigma_u, theta_u = return_substitution(s, Word([0]), coding=True)
        sage: sigma_u
        WordMorphism: 0->012, 1->02, 2->1
        sage: theta_u
        WordMorphism: 0->011, 1->01, 2->0
        sage: theta_u*sigma_u == s*theta_u
        True
        sage: theta_u*sigma_u
        WordMorphism: 0->011010, 1->0110, 2->01
    """
    from slabbe.infinite_word import derived_sequence
    a = u[0]
    x = self.fixed_point(a)
    s, D = derived_sequence(x, u, coding=True)
    _ = s[length]  # make sure that D is complete (exact value
    # is known by J. Leroy and F. Durand)
    code_to_return_word = WordMorphism({v: k for k, v in D.iteritems()})
    rep = {}
    for key, value in D.iteritems():
        self_key = self(key)
        L = desubstitute(code_to_return_word, self_key)
        if len(L) == 0:
            raise ValueError("desubstitution of {} by {} "
                             "is impossible ".format(self_key,
                                                     code_to_return_word))
        elif len(L) > 1:
            s = "=".join(["m({})".format(u) for u in L])
            msg = ("non unique desubstitution, " "{}={}".format(s, self_key))
            raise ValueError(msg)
        #print key,value,self_key,L[0]
        preimage = L[0]
        rep[value] = preimage
    m = WordMorphism(rep)
    if coding:
        return m, code_to_return_word
    else:
        return m
Ejemplo n.º 24
0
    def rauzy_move(self,
                   side='right',
                   iterations=1,
                   data=False,
                   error_on_saddles=True):
        r"""
        Performs a Rauzy move.

        INPUT:

        - ``side`` - 'left' (or 'l' or 0) or 'right' (or 'r' or 1)

        - ``iterations`` - integer (default :1) the number of iteration of Rauzy
           moves to perform

        - ``data`` - whether to return also the paths and composition of towers

        - ``error_on_saddles`` - (default: ``True``) whether to stop when a saddle
          is encountered

        OUTPUT:

        - ``iet`` -- the Rauzy move of self

        - ``path`` -- (if ``data=True``) a list of 't' and 'b'

        - ``towers`` -- (if ``data=True``) the towers of the Rauzy induction as a word morphism

        EXAMPLES::

            sage: from surface_dynamics.all import *

            sage: phi = QQbar((sqrt(5)-1)/2)
            sage: t1 = iet.IntervalExchangeTransformation(('a b','b a'),[1,phi])
            sage: t2 = t1.rauzy_move().normalize(t1.length())
            sage: l2 = t2.lengths()
            sage: l1 = t1.lengths()
            sage: l2[0] == l1[1] and l2[1] == l1[0]
            True

            sage: tt,path,sub = t1.rauzy_move(iterations=3, data=True)
            sage: tt
            Interval exchange transformation of [0, 0.3819660112501051?[ with
            permutation
            a b
            b a
            sage: path
            ['b', 't', 'b']
            sage: sub
            WordMorphism: a->aab, b->aabab

        The substitution can also be recovered from the Rauzy diagram::

            sage: p = t1.permutation()
            sage: p.rauzy_diagram().path(p, *path).substitution() == sub
            True

        An other examples involving 3 intervals::

            sage: t = iet.IntervalExchangeTransformation(('a b c','c b a'),[1,1,3])
            sage: t
            Interval exchange transformation of [0, 5[ with permutation
            a b c
            c b a
            sage: t1 = t.rauzy_move()
            sage: t1
            Interval exchange transformation of [0, 4[ with permutation
            a b c
            c a b
            sage: t2 = t1.rauzy_move()
            sage: t2
            Interval exchange transformation of [0, 3[ with permutation
            a b c
            c b a
            sage: t2.rauzy_move()
            Traceback (most recent call last):
            ...
            ValueError: saddle connection found
            sage: t2.rauzy_move(error_on_saddles=False)
            Interval exchange transformation of [0, 2[ with permutation
            a b
            a b

        Degenerate cases::

            sage: p = iet.Permutation('a b', 'b a')
            sage: T = iet.IntervalExchangeTransformation(p, [1,1])
            sage: T.rauzy_move(error_on_saddles=False)
            Interval exchange transformation of [0, 1[ with permutation
            a
            a
        """
        if data:
            towers = {a: [a] for a in self._permutation.letters()}
            path = []

        side = side_conversion(side)

        res = copy(self)
        for i in range(iterations):
            winner, (a, b,
                     c) = res._rauzy_move(side,
                                          error_on_saddles=error_on_saddles)
            if data:
                if winner is None:
                    raise ValueError("does not handle degenerate situations")
                towers[a] = towers[b] + towers[c]
                path.append(winner)

        if data:
            from sage.combinat.words.morphism import WordMorphism
            return res, path, WordMorphism(towers)
        else:
            return res
Ejemplo n.º 25
0
class GraphMap():
    """
    A GraphMap is a map from a Graph to another.  It maps a vertex to
    a vertex and an edge to an edge-path. It respects incidence
    relation. The inverse of an edge is send to the reverse path.

    AUTHORS:

    - Thierry Coulbois (2013-05-16): beta.0 version
    """
    def __init__(self, *args):
        """
        The following forms are accepted:

        - ``GraphMap(f)`` where ``f`` is a ``GraphMap``.

        - ``GraphMap(domain,codomain,edge_map,vertex_map=None)`` where
          ``domain`` and ``codomain`` are ``GraphWithInverses`` and
          ``edge_map`` is anything accepted by
          ``WordMorphism(edge_map)`` with domain alphabet an
          AlphabetWithInverses (note that only one image of the pair
          (a,inverse_letter(a)) needs to be defined for each letter).
        """
        if isinstance(args[0], GraphMap):
            self._domain = args[0]._domain
            self._codomain = args[0]._codomain
            self._edge_map = args[0]._edge_map
            self._vertex_map = args[0]._vertex_map
        else:
            self._domain = args[0]
            self._codomain = args[1]
            self.set_edge_map(args[2])
            if len(args) > 3:
                self._vertex_map = args[3]

    def __call__(self, argument):
        """
        Applies ``self`` to ``argument`` which is either a vertex of ``self`` or
        an edge path.

        SEE ALSO:

        To compute the image of a letter of the alphabet use
        ``self.image(a)``.
        """
        if self._domain.has_vertex(argument):
            if self._vertex_map == None:
                self.update_vertex_map()

            return self._vertex_map[argument]
        else:
            return self._codomain.reduce_path(self._edge_map(argument))

    def __mul__(self, other):
        """
        Compose ``self`` with ``other``.
        """
        A = other._domain.alphabet()
        result_map = {}
        for a in A.positive_letters():
            result_map[a] = self(other._edge_map.image(a))
        return GraphMap(other._domain, self._codomain, result_map)

    def __str__(self):
        """
        String represetation of ``self``.
        """
        result = "Graph map:\n" + self._domain.__str__() + "\n"
        result += self._codomain.__str__() + "\n"
        result = result + "edge map: "
        for a in self._domain._alphabet.positive_letters():
            result += a + "->" + self.image(a).__str__() + ", "
        result = result[:-2] + "\n"
        if self._vertex_map != None:
            result = result + "vertex map: " + self._vertex_map.__str__(
            ) + "\n"
        return result

    def domain(self):
        """
        Domain of ``self``: this is a graph.
        """
        return self._domain

    def codomain(self):
        """
        Codomain of ``self``: this is a graph.
        """
        return self._codomain

    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``Wordmorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None

    def compose_edge_map(self, edge_morph):
        """
        Compose ``self`` with the morphism ``edge_morph``.

        Update the edge_map of ``self`` with (``edge_morph`` o ``self``).
        """
        edge_map = dict((a, edge_morph(self._edge_map.image(a)))
                        for a in self._domain._alphabet.positive_letters())
        self.set_edge_map(edge_map)

    def update_vertex_map(self):
        """
        Computes the vertex map of ``self`` from its edge map.
        """
        vertex_map = {}
        for e in self._domain._alphabet.positive_letters():
            p = self.image(e)
            if len(p) > 0:
                vertex_map[self._domain.initial_vertex(
                    e)] = self._codomain.initial_vertex(p[0])
                vertex_map[self._domain.terminal_vertex(
                    e)] = self._codomain.terminal_vertex(p[-1])
        self._vertex_map = vertex_map

    def edge_map(self):
        """
        The edge map of ``self``: this is a word morphism.
        """
        return self._edge_map

    def image(self, letter, iter=1):
        """
        The image of a letter.

        if ``iter>1`` then returns ``self^iter(letter)``
        """

        if iter == 1:
            return self._edge_map.image(letter)
        else:
            return self._codomain.reduce_path(
                self._edge_map(self._edge_map(letter), iter - 1))

    def inverse(self):
        """A homotopy inverse of ``self``.

        For ``t1=self.domain().spanning_tree()`` and
        ``t2=self.codomain().spanning_tree()``. The alphabet ``A`` is
        made of edges not in ``t1`` identified (by their order in the
        alphabets of the domain and codomain) to letters not in
        ``t2``. The automorphism ``phi`` of ``FreeGroup(A)`` is
        defined using ``t1`` and ``t2``. The inverse map is given by
        ``phi.inverse()`` using ``t1`` and edges from ``t2`` are
        mapped to a single point (the root of ``t1``).

        In particular the inverse maps all vertices to the root of ``t1``.

        WARNING:

        ``self`` is assumed to be a homotopy equivalence.

        """

        from free_group import FreeGroup
        from free_group_automorphism import FreeGroupAutomorphism

        G1 = self.domain()
        A1 = G1.alphabet()
        t1 = G1.spanning_tree()

        G2 = self.codomain()
        A2 = G2.alphabet()
        t2 = G2.spanning_tree()

        A = AlphabetWithInverses(len(A1) - len(G1.vertices()) + 1)
        F = FreeGroup(A)

        map = dict()
        translate = dict()

        i = 0
        for a in A1.positive_letters():
            l = len(t1[G1.initial_vertex(a)]) - len(t1[G1.terminal_vertex(a)])
            if (l!=1 or t1[G1.initial_vertex(a)][-1]!=A1.inverse_letter(a)) and\
                    (l!=-1 or t1[G1.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                map[A[i]] = self(t1[G1.initial_vertex(a)] * Word([a]) *
                                 G1.reverse_path(t1[G1.terminal_vertex(a)]))
                translate[A[i]] = a
                translate[A.inverse_letter(A[i])] = A1.inverse_letter(a)
                i += 1

        rename = dict()
        edge_map = dict()

        i = 0
        for a in A2.positive_letters():
            l = len(t2[G2.initial_vertex(a)]) - len(t2[G2.terminal_vertex(a)])
            if (l!=1 or t2[G2.initial_vertex(a)][-1]!=A2.inverse_letter(a)) and\
                    (l!=-1 or t2[G2.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                rename[a] = A[i]
                rename[A2.inverse_letter(a)] = A.inverse_letter(A[i])
                i += 1
            else:
                edge_map[a] = Word()

        for a in map:
            map[a] = F([rename[b] for b in map[a] if b in rename])

        phi = FreeGroupAutomorphism(map, F)
        psi = phi.inverse()

        i = 0
        for a in A2.positive_letters():
            if a not in edge_map:
                result = Word()
                for b in psi.image(A[i]):
                    c = translate[b]
                    result = result * t1[G1.initial_vertex(c)] * Word(
                        [c]) * G1.reverse_path(t1[G1.terminal_vertex(c)])
                edge_map[a] = G1.reduce_path(result)
                i += 1

        return GraphMap(G2, G1, edge_map)

    def tighten(self):
        """
        Tighten ``self`` such that there are at least two gates at
        each vertex of the domain.

        A map is tight if for each vertex ``v`` of the domain, there
        exist reduced edge paths ``u`` and ``v`` in the domain with
        ``self(u)`` and ``self(v)`` non-trivial reduced paths starting
        with different edges.

        ``self`` and ``self.tighten()`` are homotopic.

        WARNING:

        It is assumed that ``self`` is a homotopy equivalence

        The result may send edges to trivial edge-paths.

        """
        G1 = self.domain()
        A1 = G1.alphabet()
        G2 = self.domain()

        edge_map = dict((a, self.image(a)) for a in A1)

        done = False
        while not done:
            done = True
            prefix = dict(
            )  # the common prefix of all edges outgoing from the class of a vertex
            adjacent_vertex = dict(
            )  # a class of vertices linked by a tree which is contracted by self to a point
            for a in A1:
                u = edge_map[a]
                v = G1.initial_vertex(a)
                if len(u) > 0:
                    if v not in adjacent_vertex:
                        adjacent_vertex[v] = set([v])
                    for w in adjacent_vertex[v]:
                        if w in prefix:
                            if len(prefix[w]) > 0:
                                p = G2.common_prefix_length(u, prefix[w])
                                prefix[w] = prefix[w][:p]
                            else:
                                prefix[w] = u
                else:  #we need to increase the adjacent_vertex
                    vv = G1.terminal_vertex(
                        a
                    )  #note that v!=vv because else the loop a is contracted contrary to homotopy equivalence
                    if v in adjacent_vertex and vv in adjacent_vertex:
                        adjacent_vertex[v].update(adjacent_vertex[vv])
                    elif v in adjacent_vertex:
                        adjacent_vertex[v].add(vv)
                    elif vv in adjacent_vertex:
                        adjacent_vertex[v] = adjacent_vertex[vv]
                        adjacent_vertex[v].add(v)
                    else:
                        adjacent_vertex[v] = set([v, vv])
                    if v in prefix:
                        prefixv = prefix[v]
                    else:
                        prefixv = False
                    for w in adjacent_vertex[v]:
                        if v != w:
                            adjacent_vertex[w] = adjacent_vertex[v]
                            if w in prefix:
                                if prefixv:
                                    p = G2.common_prefix_length(
                                        prefixv, prefix[w])
                                    prefixv = prefixv[:p]
                                else:
                                    prefixv = prefix[w]
                    if prefixv:
                        for w in adjacent_vertex[v]:
                            prefix[w] = prefixv

            for a in A1:
                v = G1.initial_vertex(a)
                if v in prefix and len(prefix[v]) > 0:
                    done = False
                    aa = A1.inverse_letter(a)
                    if len(edge_map[a]) > 0:
                        edge_map[a] = edge_map[a][len(prefix[v]):]
                        edge_map[aa] = edge_map[aa][:-len(prefix[v])]
                    # else:
                    #     edge_map[a]=G2.reverse_path(prefix[v])
                    #     edge_map[aa]=prefix[v]
                    break

        self.set_edge_map(edge_map)

        return self

    def subdivide_domain(self, e):
        """
        Subdivide an edge in the domain graph. 

        The edge ``e`` is subdivided into ``l`` edges where ``l`` is
        the length of the image of ``e`` by ``self``.

        Update the edge map of ``self``.

        Intended to be used by Stalling's folding algorithm to get an
        immersion.

        SEE ALSO::

        GraphWithInverses.subdivide_edge()

        """
        G = self.domain()
        A = G.alphabet()
        result_map = dict((a, Word([a])) for a in A)
        w = self.image(e)
        new_edges = A.add_new_letters(len(w) - 1)
        new_vertices = G.new_vertices(len(w) - 1)
        d = {(a, self.image(a)) for a in A.positive_letters()}
        for i, a in enumerate(new_edges):
            v = new_vertices[i]
            if i == 0:
                vi = G.initial_vertex(e)
                vt = G.terminal_vertex(e)
                f = new_edges[i][0]
                ee = A.inverse_letter(e)
                ff = new_edges[i][1]
                G.set_terminal_vertex(e, v)
                G.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]
            else:
                vi = self._domain.initial_vertex(new_edges[i - 1][0])
                vt = self._domain.terminal_vertex(new_edges[i - 1][0])
                f = new_edges[i][0]
                #ee=A.inverse_letter(e) #already done
                ff = new_edges[i][1]
                self._domain.set_terminal_vertex(new_edges[i - 1][0], v)
                self._domain.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]

        # updating self.edge_map after subdivision
        if A.is_positive_letter(e):
            d[e] = Word([self.image(e)[0]])
        else:
            d[ee] = Word([self.image(ee)[-1]])

        self.set_edge_map(d)

        return result_map

    def illegal_turns(self, turns=None):
        """
        List of illegal turns in the domain graph.

        A turn is illegal if it is mapped by ``self`` to a degenerate
        turn.

        INPUT:

        ``turns`` a list of turns of the domain graph. Default is None
        meaning all the turns of the graph. I not ``None`` return the
        sublist of ``turns`` consisting of illegal turns.
        """
        result = []

        if turns is None:
            turns = self.domain().turns()

        for turn in turns:
            u = self.image(turn[0])
            v = self.image(turn[1])
            if len(u) == 0 or len(v) == 0 or u[0] == v[0]:
                result.append(turn)
        return result

    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``. 

        The domain of ``self`` is fold until we get an immersion. 

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.  

        Then fold one gate at one vertex and update the edge map and
        illegal turns list. 

        Repeat the process till no illegal turns remain.

        REFERENCES:

        [Stallings] J. Stallings, Topology of Finite Graphs,
        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) > 1:
                self.subdivide_domain(a)

        Turns = self._domain.turns(
        )  #list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(
            Turns)  # list of illegal turns in domain after subdivision
        counter = 0
        while len(Il_turns) > 0:
            counter = counter + 1

            # find edge_list (list of edges in the gate correspoding to e1) to fold at exactly one vertex
            e1 = Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(
                        a) == self._domain.initial_vertex(e1) and e1 != a:
                    if (e1, a) in Il_turns or (a, e1) in Il_turns:
                        edge_list.append(a)

            edge_list = list(set(edge_list))  # remove duplicates
            # fold at initial_vertex of e1 ( this function updates the domain and edge_map)
            self._domain.fold(edge_list, [])

            #update edge_map again
            d = {}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)
            Turns = self._domain.turns()  #update list of all turns in domain
            Il_turns = self.illegal_turns(
                Turns)  # update list of illegal turns in domain

        return self

    def pullback(self, f2, G3, A):
        """
        INPUT : Two Graph maps f1:G1->G, f2:G2->G, an empty GraphWithInverses G3 and an empty AlphabetWithInverses A
        OPERATION : Find the pullback G3 and a graph map f:G3->G
        OUTPUT : Graphmap f 
        
        The pullback method can be used to find intersection of two subgroups of a Free Group. 
        
        Example : 
        G1 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='x0')) 
        G2 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='a0')) 
        G =  GraphWithInverses.rose_graph(AlphabetWithInverses(2)) 
        n1 = WordMorphism({'x0':['a','a'],'x1':['b','a']})
        n2 = WordMorphism({'a0':['b','a'],'a1':['b','b','b','a','B','a']})
        f1 = GraphMap(G1,G,n1)
        f2 = GraphMap(G2,G,n2)
        G3 = GraphWithInverses()
        A = AlphabetWithInverses(0,type='a0')
        
        f1.pullback(f2,G3,A)
        """
        import itertools
        #First convert self and f2 into immersions
        self.stallings_folding()
        f2.stallings_folding()

        # G3 = GraphWithInverses()
        #A = AlphabetWithInverses(0,type='a0')
        d = {}
        #get set of vertices
        V = []
        for i in itertools.product(self.domain().vertices(),
                                   f2.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 f2.domain().alphabet().positive_letters():
                            if f2.domain().initial_vertex(
                                    e2) == v[1] and f2.domain(
                                    ).terminal_vertex(e2) == w[1]:
                                if self.image(e1) == f2.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 f2.codomain()

        return GraphMap(G3, G, n3)

    @staticmethod
    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``.
        """

        graph = GraphWithInverses.rose_graph(
            automorphism.domain().alphabet().copy())
        return GraphMap(graph, graph, automorphism)
class GraphMap:
    """
    A GraphMap is a map from a Graph to another.  It maps a vertex to
    a vertex and an edge to an edge-path. It respects incidence
    relation. The inverse of an edge is send to the reverse path.

    AUTHORS:

    - Thierry Coulbois (2013-05-16): beta.0 version
    """

    def __init__(self, *args):
        """
        The following forms are accepted:

        - ``GraphMap(f)`` where ``f`` is a ``GraphMap``.
        - ``GraphMap(domain, codomain, edge_map, vertex_map=None)`` where
          ``domain`` and ``codomain`` are ``GraphWithInverses`` and
          ``edge_map`` is anything accepted by
          ``WordMorphism(edge_map)`` with domain alphabet an
          AlphabetWithInverses (note that only one image of the pair
          (a,inverse_letter(a)) needs to be defined for each letter).

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: print GraphMap(G,H,"a->ab,b->b,c->B")
            Graph map:
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->ab, b->b, c->B
        """
        if isinstance(args[0], GraphMap):
            self._domain = args[0]._domain
            self._codomain = args[0]._codomain
            self._edge_map = args[0]._edge_map
            self._vertex_map = args[0]._vertex_map
        else:
            self._domain = args[0]
            self._codomain = args[1]
            self.set_edge_map(args[2])
            if len(args) > 3:
                self._vertex_map = args[3]

    def __call__(self, argument):
        """
        Applies ``self`` to ``argument`` which is either a vertex
         of ``self`` or an edge path.

        INPUT:

        -``argument`` a vertex of ``self`` or a edge path

        OUTPUT:

        A vertex of ``self`` or an edge path
        Applies ``self`` to ``argument`` which is either .

        SEE ALSO:

        To compute the image of a letter of the alphabet use
        ``self.image(a)``.

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f('abc')
            word: ab
        """
        if self._domain.has_vertex(argument):
            if self._vertex_map is None:
                self.update_vertex_map()

            return self._vertex_map[argument]
        else:
            return self._codomain.reduce_path(self._edge_map(argument))

    def __mul__(self, other):
        """
        Compose ``self`` with ``other``.

        INPUT:

        - ``other`` other GraphMap to compute multiplication

        OUTPUT:

        GraphMap obtains with other domain as first
        input and ``self`` codomain for second input and vertex_map is
        construct applying ``self`` to ``other`` edge_map image

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: g = GraphMap(H,H,"a->aba,b->ba")
            sage: print g * f
            Graph map:
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->ababa, b->ba, c->AB
        """
        A = other._domain.alphabet()
        result_map = {}
        for a in A.positive_letters():
            result_map[a] = self(other._edge_map.image(a))
        return GraphMap(other._domain, self._codomain, result_map)

    def __str__(self):
        """
        String represetation of ``self``.

        OUTPUT:

        a string representation of ``self``

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: GraphMap(G,H,"a->ab,b->b,c->B").__str__()
            'Graph map:\nGraph with inverses: a: 0->0, b: 0->1, c: 1->1\nGraph with inverses: a: 0->0, b: 0->0\nedge map: a->ab, b->b, c->B'
        """
        result = "Graph map:\n" + self._domain.__str__() + "\n"
        result += self._codomain.__str__() + "\n"
        result += "edge map: "
        for a in self._domain._alphabet.positive_letters():
            result += a + "->" + self.image(a).__str__() + ", "
        result = result[:-2] # + "\n"
        if self._vertex_map is not None:
            result = result + "\n"
            result = result + "vertex map: " +\
                self._vertex_map.__str__() # + "\n"
        return result

    def domain(self):
        """
        Domain of ``self``: this is a graph.

        OUTPUT:

        Domain of ``self`` which is a GrapMap

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print f.domain()
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
        """
        return self._domain

    def codomain(self):
        """
        Codomain of ``self``: this is a graph.

        OUTPUT:

        Codomain of ``self`` which is a GrapMap

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print f.codomain()
            Graph with inverses: a: 0->0, b: 0->0
        """
        return self._codomain

    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``WordMorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        INPUT:

        - ``edge_map``: anything which is accepted by ``WordMorphism(edge_map)``

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.set_edge_map('a->b,b->,c->b')
            sage: print f
            Graph map:
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->b, b->, c->b
        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None

    def compose_edge_map(self, edge_morph):
        """Compose ``self`` with the morphism ``edge_morph``.

        Update the edge_map of ``self`` with (``edge_morph`` o ``self``).
        
        INPUT:

        - ``edge_morph``: A ``WordMorphism`` from the alphabet labeling
          the codomain of ``self`` to itself.

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.compose_edge_map(FreeGroupAutomorphism('a->aba,b->ba'))
            sage: print f
            Graph map:
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->ababa, b->ba, c->AB
        """
        edge_map = dict((a, edge_morph(self._edge_map.image(a))) for a in
                        self._domain._alphabet.positive_letters())
        self.set_edge_map(edge_map)

    def update_vertex_map(self):
        """
        Computes the vertex map of ``self`` from its edge map.

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.update_vertex_map()
            sage: print f
            Graph map:
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->ab, b->b, c->B
            vertex map: {0: 0, 1: 0}
        """
        vertex_map = {}
        for e in self._domain._alphabet.positive_letters():
            p = self.image(e)
            if len(p) > 0:
                vertex_map[self._domain.initial_vertex(e)] = \
                    self._codomain.initial_vertex(p[0])
                vertex_map[self._domain.terminal_vertex(e)] = \
                    self._codomain.terminal_vertex(p[-1])
        self._vertex_map = vertex_map

    def edge_map(self):
        """
        The edge map of ``self``: this is a word morphism.

        OUTPUT:
        The edge map of ``self``

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.edge_map()
            WordMorphism: A->BA, B->B, C->b, a->ab, b->b, c->B
        """
        return self._edge_map

    def image(self, letter, iter=1):
        """The image of a letter.

        if ``iter>1`` then returns ``self^iter(letter)``

        INPUT:
            
        - ``iter``: -- (default 1) a positive integer
        - ``letter``: a letter of the alphabet of the domain of ``self``.

        OUTPUT:

        if ``iter`` > 1 then returns ``self``^iter(letter)``
        if ``iter`` = 1then returns the image of letter

        WARNING:

        ``iter`` may be greater than 1 only if the domain and codomain
        of ``self`` are equal (that is to say, ``self`` is a
        GraphSelfMap)

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.image('a')
            word: ab
        """

        if iter == 1:
            return self._edge_map.image(letter)
        else:
            return self._codomain.reduce_path(
                self._edge_map(self._edge_map(letter), iter - 1))

    def inverse(self):
        """A homotopy inverse of ``self``.

        For ``t1=self.domain().spanning_tree()`` and
        ``t2=self.codomain().spanning_tree()``. The alphabet ``A`` is
        made of edges not in ``t1`` identified (by their order in the
        alphabets of the domain and codomain) to letters not in
        ``t2``. The automorphism ``phi`` of ``FreeGroup(A)`` is
        defined using ``t1`` and ``t2``. The inverse map is given by
        ``phi.inverse()`` using ``t1`` and edges from ``t2`` are
        mapped to a single point (the root of ``t1``).

        In particular the inverse maps all vertices to the root of ``t1``.

        OUTPUT:
        A homotopy inverse of ``self``.

        WARNING:

        ``self`` is assumed to be a homotopy equivalence.

        EXAMPLES::

            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print f.inverse()
            Graph map:
            Graph with inverses: a: 0->0, b: 0->0
            Graph with inverses: a: 0->0, b: 0->1, c: 1->1
            edge map: a->abcB, b->bCB
        """

        from free_group import FreeGroup
        from free_group_automorphism import FreeGroupAutomorphism

        g1 = self.domain()
        a1 = g1.alphabet()
        t1 = g1.spanning_tree()

        g2 = self.codomain()
        a2 = g2.alphabet()
        t2 = g2.spanning_tree()

        A = AlphabetWithInverses(len(a1) - len(g1.vertices()) + 1)
        f = FreeGroup(A)

        map = dict()
        translate = dict()

        i = 0
        for a in a1.positive_letters():
            l = len(t1[g1.initial_vertex(a)]) - len(t1[g1.terminal_vertex(a)])
            if (l != 1 or t1[g1.initial_vertex(a)][-1] != a1.inverse_letter(
                    a)) and (l != -1 or t1[g1.terminal_vertex(a)][-1] != a):
                # a is not in the spanning tree
                map[A[i]] = self(
                    t1[g1.initial_vertex(a)] * Word([a]) * g1.reverse_path(
                        t1[g1.terminal_vertex(a)]))
                translate[A[i]] = a
                translate[A.inverse_letter(A[i])] = a1.inverse_letter(a)
                i += 1

        rename = dict()
        edge_map = dict()

        i = 0
        for a in a2.positive_letters():
            l = len(t2[g2.initial_vertex(a)]) - len(t2[g2.terminal_vertex(a)])
            if (l != 1 or t2[g2.initial_vertex(a)][-1] != a2.inverse_letter(
                    a)) and (l != -1 or t2[g2.terminal_vertex(a)][-1] != a):
                # a is not in the spanning tree
                rename[a] = A[i]
                rename[a2.inverse_letter(a)] = A.inverse_letter(A[i])
                i += 1
            else:
                edge_map[a] = Word()

        for a in map:
            map[a] = f([rename[b] for b in map[a] if b in rename])

        phi = FreeGroupAutomorphism(map, f)
        psi = phi.inverse()

        i = 0
        for a in a2.positive_letters():
            if a not in edge_map:
                result = Word()
                for b in psi.image(A[i]):
                    c = translate[b]
                    result = result * t1[g1.initial_vertex(c)] * Word(
                        [c]) * g1.reverse_path(t1[g1.terminal_vertex(c)])
                edge_map[a] = g1.reduce_path(result)
                i += 1

        return GraphMap(g2, g1, edge_map)

    def tighten(self):
        """
        Tighten ``self`` such that there are at least two gates at
        each vertex of the domain.

        A map is tight if for each vertex ``v`` of the domain, there
        exist reduced edge paths ``u`` and ``v`` in the domain with
        ``self(u)`` and ``self(v)`` non-trivial reduced paths starting
        with different edges.

        ``self`` and ``self.tighten()`` are homotopic.

        OUTPUT:

        Tighten ``self`` such that there are at least two gates at
        each vertex of the domain.

        WARNING:

        It is assumed that ``self`` is a homotopy equivalence

        The result may send edges to trivial edge-paths.

        EXAMPLES::

            sage: A = AlphabetWithInverses(2)
            sage: G = GraphWithInverses.rose_graph(A)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->baabAB,b->babAB")
            sage: f.tighten()
            WordMorphism: A->BA, B->B, a->ab, b->b
            sage: print f
            Graph map:
            Graph with inverses: a: 0->0, b: 0->0
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->ab, b->b
        """
        G1 = self.domain()
        A1 = G1.alphabet()
        G2 = self.codomain()

        edge_map = dict((a, self.image(a)) for a in A1)

        done = False
        while not done:
            done = True
            prefix = dict()  # the common prefix of all edges outgoing from
            # the class of a vertex
            adjacent_vertex = dict()  # a class of vertices linked by a tree
            # which is contracted by self to a point
            for a in A1:
                u = edge_map[a]
                v = G1.initial_vertex(a)
                if len(u) > 0:
                    if v not in adjacent_vertex:
                        adjacent_vertex[v] = set([v])
                    for w in adjacent_vertex[v]:
                        if w in prefix:
                            if len(prefix[w]) > 0:
                                p = G2.common_prefix_length(u, prefix[w])
                                prefix[w] = prefix[w][:p]
                        else:
                            prefix[w] = u
                else:  # we need to increase the adjacent_vertex
                    vv = G1.terminal_vertex(a)
                    # note that v!=vv because else the loop a is contracted
                    # contrary to homotopy equivalence
                    if v in adjacent_vertex and vv in adjacent_vertex:
                        adjacent_vertex[v].update(adjacent_vertex[vv])
                    elif v in adjacent_vertex:
                        adjacent_vertex[v].add(vv)
                    elif vv in adjacent_vertex:
                        adjacent_vertex[v] = adjacent_vertex[vv]
                        adjacent_vertex[v].add(v)
                    else:
                        adjacent_vertex[v] = set([v, vv])
                    if v in prefix:
                        prefixv = prefix[v]
                    else:
                        prefixv = False
                    for w in adjacent_vertex[v]:
                        if v != w:
                            adjacent_vertex[w] = adjacent_vertex[v]
                            if w in prefix:
                                if prefixv:
                                    p = G2.common_prefix_length(prefixv,
                                                                prefix[w])
                                    prefixv = prefixv[:p]
                                else:
                                    prefixv = prefix[w]
                    if prefixv:
                        for w in adjacent_vertex[v]:
                            prefix[w] = prefixv
            
            for a in A1:
                v = G1.initial_vertex(a)
                if v in prefix and len(prefix[v]) > 0:
                    done = False
                    aa = A1.inverse_letter(a)
                    if len(edge_map[a]) > 0:
                        edge_map[a] = edge_map[a][len(prefix[v]):]
                        edge_map[aa] = edge_map[aa][:-len(prefix[v])]

        self.set_edge_map(edge_map)

        return self._edge_map

    def subdivide_domain(self, e):
        """
        Subdivide an edge in the domain graph.

        The edge ``e`` is subdivided into ``l`` edges where ``l`` is
        the length of the image of ``e`` by ``self``.

        Update the edge map of ``self``.

        Intended to be used by Stalling's folding algorithm to get an
        immersion.

        ...SEE ALSO::

            :meth:`sage.combinat.words.GraphWithInverses.subdivide_edge()`

        INPUT:

        - ``e``: and edge of the domain of ``self``.

        OUTPUT:

        A dictionnary that maps old edges of the domain to new edges of the domain.

        EXAMPLES::

            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: f.subdivide_domain('a')
            {'A': word: DCA, 'B': word: B, 'a': word: acd, 'b': word: b}
            sage: print f
            Graph map:
            Graph with inverses: a: 0->1, b: 0->0, c: 1->2, d: 2->0
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->a, b->ab, c->b, d->a
        """
        G = self.domain()
        A = G.alphabet()
        result_map = dict((a, Word([a])) for a in A)
        w = self.image(e)
        d = dict((a, self.image(a)) for a in A.positive_letters())
        new_edges = A.add_new_letters(len(w) - 1)
        new_vertices = G.new_vertices(len(w) - 1)
        for i, a in enumerate(new_edges):
            v = new_vertices[i]
            if i == 0:
                vi = G.initial_vertex(e)
                vt = G.terminal_vertex(e)
                f = new_edges[i][0]
                ee = A.inverse_letter(e)
                ff = new_edges[i][1]
                G.set_terminal_vertex(e, v)
                G.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]
            else:
                vi = self._domain.initial_vertex(new_edges[i - 1][0])
                vt = self._domain.terminal_vertex(new_edges[i - 1][0])
                f = new_edges[i][0]
                # ee=A.inverse_letter(e) #already done
                ff = new_edges[i][1]
                self._domain.set_terminal_vertex(new_edges[i - 1][0], v)
                self._domain.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]

        # updating self.edge_map after subdivision
        if A.is_positive_letter(e):
            d[e] = Word([self.image(e)[0]])
        else:
            d[ee] = Word([self.image(ee)[-1]])

        self.set_edge_map(d)

        return result_map

    def illegal_turns(self, turns=None):
        """
        List of illegal turns in the domain graph.

        A turn is illegal if it is mapped by ``self`` to a degenerate
        turn.

        INPUT:

        - ``turns`` a list of turns of the domain graph. Default is None
          meaning all the turns of the graph. I not ``None`` return the
          sublist of ``turns`` consisting of illegal turns.


        EXAMPLES::

            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: f.illegal_turns()
            [('a', 'b')]
        """
        result = []

        if turns is None:
            turns = self.domain().turns()

        for turn in turns:
            u = self.image(turn[0])
            v = self.image(turn[1])
            if len(u) == 0 or len(v) == 0 or u[0] == v[0]:
                result.append(turn)
        return result

    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``.

        The domain of ``self`` is fold until we get an
        immersion. Intended to be used to compute the pullback of two
        graph maps and the intersection of subgroupes of a free group.

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.

        Then fold one gate at one vertex and update the edge map and
        illegal turns list.

        Repeat the process till no illegal turns remain.

        EXAMPLES::

            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: f.stallings_folding()
            sage: print f
            Graph map:
            Graph with inverses: a: 1->1, c: 1->1
            Graph with inverses: a: 0->0, b: 0->0
            edge map: a->a, c->b

        REFERENCES:

        [Stallings] J. Stallings, Topology of Finite Graphs,

        AUTHOR:

        - Radhika GUPTA

        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) > 1:
                self.subdivide_domain(a)

        Turns = self._domain.turns()
        # list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(Turns)
        # list of illegal turns in domain after subdivision
        counter = 0
        while len(Il_turns) > 0:
            counter = counter + 1

            # find edge_list (list of edges in the gate correspoding to e1)
            # to fold at exactly one vertex
            e1 = Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(a) == \
                        self._domain.initial_vertex(e1) and e1 != a:
                    if (e1, a) in Il_turns or (a, e1) in Il_turns:
                        edge_list.append(a)

            edge_list = list(set(edge_list))  # remove duplicates
            # fold at initial_vertex of e1 ( this function updates the
            # domain and edge_map)
            self._domain.fold(edge_list, [])

            # update edge_map again
            d = {}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)
            Turns = self._domain.turns()  # update list of all turns in domain
            Il_turns = self.illegal_turns(Turns)
            # update list of illegal turns in domain

    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)

    @staticmethod
    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)
Ejemplo n.º 27
0
    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 : G_3 \to 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 `G_2 \to G` with ``self`` (a
          graph map `G_1 \to G`)

        OUTPUT:

        A ``GraphMap``.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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:
            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)
            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)
Ejemplo n.º 28
0
    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``.

        The domain of ``self`` is fold until we get an
        immersion. Intended to be used to compute the pullback of two
        graph maps and the intersection of subgroupes of a free group.

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.

        Then fold one gate at one vertex and update the edge map and
        illegal turns list.

        Repeat the process till no illegal turns remain.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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: f.stallings_folding()
            sage: print(f)
            Graph map:
            a: 1->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->a, c->b

        REFERENCES:

        .. [Stallings] J. Stallings, Topology of Finite Graphs,

        AUTHOR:

            - Radhika GUPTA

        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) > 1:
                self.subdivide_domain(a)

        Turns = self._domain.turns()
        # list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(Turns)
        # list of illegal turns in domain after subdivision
        counter = 0
        while len(Il_turns) > 0:
            counter = counter + 1

            # find edge_list (list of edges in the gate correspoding to e1)
            # to fold at exactly one vertex
            e1 = Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(a) == \
                        self._domain.initial_vertex(e1) and e1 != a:
                    if (e1, a) in Il_turns or (a, e1) in Il_turns:
                        edge_list.append(a)

            edge_list = list(set(edge_list))  # remove duplicates
            # fold at initial_vertex of e1 ( this function updates the
            # domain and edge_map)
            self._domain.fold(edge_list, [])

            # update edge_map again
            d = {}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)
            Turns = self._domain.turns()  # update list of all turns in domain
            Il_turns = self.illegal_turns(Turns)
Ejemplo n.º 29
0
class GraphMap():
    """
    A GraphMap is a map from a Graph to another.  It maps a vertex to
    a vertex and an edge to an edge-path. It respects incidence
    relation. The inverse of an edge is send to the reverse path.

    AUTHORS:

    - Thierry Coulbois (2013-05-16): beta.0 version
    """
    def __init__(self, *args):
        """
        The following forms are accepted:

        - ``GraphMap(f)`` where ``f`` is a ``GraphMap``.
        - ``GraphMap(domain, codomain, edge_map, vertex_map=None)`` where
          ``domain`` and ``codomain`` are ``GraphWithInverses`` and
          ``edge_map`` is anything accepted by
          ``WordMorphism(edge_map)`` with domain alphabet an
          AlphabetWithInverses (note that only one image of the pair
          (a,inverse_letter(a)) needs to be defined for each letter).

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: print(GraphMap(G,H,"a->ab,b->b,c->B"))
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->ab, b->b, c->B
        """
        if isinstance(args[0], GraphMap):
            self._domain = args[0]._domain
            self._codomain = args[0]._codomain
            self._edge_map = args[0]._edge_map
            self._vertex_map = args[0]._vertex_map
        else:
            self._domain = args[0]
            self._codomain = args[1]
            self.set_edge_map(args[2])
            if len(args) > 3:
                self._vertex_map = args[3]

    def __call__(self, argument):
        """
        Applies ``self`` to ``argument`` which is either a vertex
         of ``self`` or an edge path.

        INPUT:

        -``argument`` a vertex of ``self`` or a edge path

        OUTPUT:

        A vertex of ``self`` or an edge path
        Applies ``self`` to ``argument`` which is either .

        .. SEEALSO::

            To compute the image of a letter of the alphabet use
            :meth:`train_track.graph_map.GraphMap.image(a)``.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f('abc')
            word: ab
        """
        if self._domain.has_vertex(argument):
            if self._vertex_map is None:
                self.update_vertex_map()

            return self._vertex_map[argument]
        else:
            return self._codomain.reduce_path(self._edge_map(argument))

    def __mul__(self, other):
        """
        Compose ``self`` with ``other``.

        INPUT:

        - ``other`` -- other GraphMap to compute multiplication

        OUTPUT:

        GraphMap obtains with other domain as first
        input and ``self`` codomain for second input and vertex_map is
        construct applying ``self`` to ``other`` edge_map image

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: g = GraphMap(H,H,"a->aba,b->ba")
            sage: print(g * f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->ababa, b->ba, c->AB
        """
        A = other._domain.alphabet()
        result_map = {}
        for a in A.positive_letters():
            result_map[a] = self(other._edge_map.image(a))
        return GraphMap(other._domain, self._codomain, result_map)

    def __repr__(self):
        """
        String represetation of ``self``.

        OUTPUT:

        a string representation of ``self``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: GraphMap(G,H,"a->ab,b->b,c->B").__repr__()
            'Graph map:\na: 0->0, b: 0->1, c: 1->1\na: 0->0, b: 0->0\nedge map: a->ab, b->b, c->B'
        """
        result = "Graph map:\n" + self._domain.__str__() + "\n"
        result += self._codomain.__str__() + "\n"
        result += "edge map: "
        for a in self._domain._alphabet.positive_letters():
            result += a + "->" + self.image(a).__str__() + ", "
        result = result[:-2]  # + "\n"
        if self._vertex_map is not None:
            result = result + "\n"
            result = result + "vertex map: " +\
                self._vertex_map.__str__() # + "\n"
        return result

    def domain(self):
        """
        Domain of ``self``: this is a graph.

        OUTPUT:

        Domain of ``self`` which is a GrapMap

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print(f.domain())
            a: 0->0, b: 0->1, c: 1->1
        """
        return self._domain

    def codomain(self):
        """
        Codomain of ``self``: this is a graph.

        OUTPUT:

        Codomain of ``self`` which is a GrapMap

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print(f.codomain())
            a: 0->0, b: 0->0
        """
        return self._codomain

    def set_edge_map(self, edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``WordMorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        INPUT:

        - ``edge_map`` -- anything which is accepted by ``WordMorphism(edge_map)``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.set_edge_map('a->b,b->,c->b')
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->b, b->, c->b
        """
        A = self.domain().alphabet()
        tmp_map = WordMorphism(edge_map)
        m = {}
        for a in tmp_map._domain.alphabet():
            m[a] = self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)] = self._codomain.reverse_path(m[a])
        self._edge_map = WordMorphism(m)
        self._vertex_map = None

    def compose_edge_map(self, edge_morph):
        """Compose ``self`` with the morphism ``edge_morph``.

        Update the edge_map of ``self`` with (``edge_morph`` o ``self``).

        INPUT:

        - ``edge_morph`` -- A ``WordMorphism`` from the alphabet labeling
          the codomain of ``self`` to itself.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.compose_edge_map(FreeGroupAutomorphism('a->aba,b->ba'))
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->ababa, b->ba, c->AB
        """
        edge_map = dict(
            (a,
             edge_morph.to_word_morphism(use_str=True,
                                         upper_case_as_inverse=True)(
                                             self._edge_map.image(a)))
            for a in self._domain._alphabet.positive_letters())
        self.set_edge_map(edge_map)

    def update_vertex_map(self):
        """
        Computes the vertex map of ``self`` from its edge map.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.update_vertex_map()
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->ab, b->b, c->B
            vertex map: {0: 0, 1: 0}
        """
        vertex_map = {}
        for e in self._domain._alphabet.positive_letters():
            p = self.image(e)
            if len(p) > 0:
                vertex_map[self._domain.initial_vertex(e)] = \
                    self._codomain.initial_vertex(p[0])
                vertex_map[self._domain.terminal_vertex(e)] = \
                    self._codomain.terminal_vertex(p[-1])
        self._vertex_map = vertex_map

    def vertex_map(self):
        """
        The vertex map of ``self``: this is a dictionnary.

        OUTPUT:

        The vertex map of ``self``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.vertex_map()
            {0: 0, 1: 0}
        """
        if self._vertex_map is None:
            self.update_vertex_map()
        return self._vertex_map

    def edge_map(self):
        """
        The edge map of ``self``: this is a word morphism.

        OUTPUT:

        The edge map of ``self``

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.edge_map()
            WordMorphism: A->BA, B->B, C->b, a->ab, b->b, c->B
        """
        return self._edge_map

    def image(self, letter, iter=1):
        """The image of a letter.

        if ``iter>1`` then returns ``self^iter(letter)``

        INPUT:

        - ``iter`` (default: 1) -- a positive integer
        - ``letter`` -- a letter of the alphabet of the domain of ``self``.

        OUTPUT:

        if ``iter`` > 1 then returns ``self``^iter(letter)``
        if ``iter`` = 1then returns the image of letter

        .. WARNING::

            ``iter`` may be greater than 1 only if the domain and codomain
            of ``self`` are equal (that is to say, ``self`` is a
            GraphSelfMap)

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: f.image('a')
            word: ab
        """
        if iter == 1:
            return self._edge_map.image(letter)
        else:
            return self._codomain.reduce_path(
                self._edge_map(self._edge_map(letter), iter - 1))

    def inverse(self):
        """A homotopy inverse of ``self``.

        For ``t1=self.domain().spanning_tree()`` and
        ``t2=self.codomain().spanning_tree()``. The alphabet ``A`` is
        made of edges not in ``t1`` identified (by their order in the
        alphabets of the domain and codomain) to letters not in
        ``t2``. The automorphism ``phi`` of ``FreeGroup(A)`` is
        defined using ``t1`` and ``t2``. The inverse map is given by
        ``phi.inverse()`` using ``t1`` and edges from ``t2`` are
        mapped to a single point (the root of ``t1``).

        In particular the inverse maps all vertices to the root of ``t1``.

        OUTPUT:

        A homotopy inverse of ``self``.

        .. WARNING::

            ``self`` is assumed to be a homotopy equivalence.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: G = GraphWithInverses([[0,0,'a'],[0,1,'b'],[1,1,'c']])
            sage: A = AlphabetWithInverses(2)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->ab,b->b,c->B")
            sage: print(f.inverse())
            Graph map:
            a: 0->0, b: 0->0
            a: 0->0, b: 0->1, c: 1->1
            edge map: a->abcB, b->bCB
        """

        from .free_group import FreeGroup
        from .free_group_automorphism import FreeGroupAutomorphism

        G1 = self.domain()
        A1 = G1.alphabet()
        T1 = G1.spanning_tree()

        G2 = self.codomain()
        A2 = G2.alphabet()
        T2 = G2.spanning_tree()

        A = AlphabetWithInverses(len(A1) - len(G1.vertices()) + 1)
        F = FreeGroup(A.positive_letters())

        map = dict()
        translate = dict()

        i = 0
        for a in A1.positive_letters():
            l = len(T1[G1.initial_vertex(a)]) - len(T1[G1.terminal_vertex(a)])
            if (l != 1 or T1[G1.initial_vertex(a)][-1] != A1.inverse_letter(a)
                ) and (l != -1 or T1[G1.terminal_vertex(a)][-1] != a):
                # a is not in the spanning tree
                map[A[i]] = self(T1[G1.initial_vertex(a)] * Word([a]) *
                                 G1.reverse_path(T1[G1.terminal_vertex(a)]))
                translate[A[i]] = a
                translate[A.inverse_letter(A[i])] = A1.inverse_letter(a)
                i += 1
        rename = dict()
        edge_map = dict()

        i = 0
        for a in A2.positive_letters():
            l = len(T2[G2.initial_vertex(a)]) - len(T2[G2.terminal_vertex(a)])
            if (l != 1 or T2[G2.initial_vertex(a)][-1] != A2.inverse_letter(a)
                ) and (l != -1 or T2[G2.terminal_vertex(a)][-1] != a):
                # a is not in the spanning tree
                rename[a] = A[i]
                rename[A2.inverse_letter(a)] = A.inverse_letter(A[i])
                i += 1
            else:
                edge_map[a] = Word()

        for a in map:
            map[a] = F([rename[b] for b in map[a] if b in rename])

        phi = FreeGroupAutomorphism(map, F)
        psi = phi.inverse()
        i = 0
        for a in A2.positive_letters():
            if a not in edge_map:
                result = Word()
                for b in psi(A[i]).to_word():
                    c = translate[b]
                    result = result * T1[G1.initial_vertex(c)] * Word(
                        [c]) * G1.reverse_path(T1[G1.terminal_vertex(c)])
                edge_map[a] = G1.reduce_path(result)
                i += 1

        return GraphMap(G2, G1, edge_map)

    def tighten(self, verbose=False):
        """
        Tighten ``self`` such that there are at least three gates at each
        vertex of the domain.

        A vertex has at least three gates if there exists outgoing
        reduced paths ``u``, ``v``, ``w`` with the reduced paths ``f(u)``,
        ``f(v)`` and ``f(w)`` starting with different edges.

        A map is tight if the tuple ``(f(e1),f(e2),...)`` is minimal
        first for the sum of the length, then in lexicographic order.

        ``self`` and ``self.tighten()`` are homotopic.

        OUTPUT:

        Edge map of tighten ``self`` such that there are at least two
        gates at each vertex of the domain.

        .. WARNING::

            It is assumed that ``self`` is injective in homotopy.

            The result may send edges to trivial edge-paths.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: A = AlphabetWithInverses(2)
            sage: G = GraphWithInverses.rose_graph(A)
            sage: H = GraphWithInverses.rose_graph(A)
            sage: f = GraphMap(G,H,"a->baabAB,b->babAB")
            sage: f.tighten()
            WordMorphism: A->AB, B->B, a->ba, b->b
            sage: print(f)
            Graph map:
            a: 0->0, b: 0->0
            a: 0->0, b: 0->0
            edge map: a->ba, b->b

        """
        G1 = self.domain()
        A1 = G1.alphabet()
        G2 = self.codomain()

        edge_map = dict((a, self.image(a)) for a in A1)

        adjacent_vertex = dict((v, v) for v in G1.vertices())  # a
        # class of vertices linked by a tree which is contracted by
        # self to a point

        done = False
        while not done:
            done = True
            gates = dict((v, []) for v in G1.vertices())  # the gates
            # outgoing from a contracted tree
            minimal_edge = dict()  # The minimal edge (in lexicographic
            # order) outgoing from a contracted forest.

            for a in A1:
                u = edge_map[a]
                v = G1.initial_vertex(a)
                vv = adjacent_vertex[v]
                if len(u) > 0:
                    gates[vv].append(u)
                    if vv not in minimal_edge or a < minimal_edge[vv]:
                        minimal_edge[vv] = a

                else:  # we need to increase the adjacent_vertex
                    w = G1.terminal_vertex(a)
                    ww = adjacent_vertex[w]
                    if vv != ww:  # we need to fusion the two contracted forests
                        gates[vv] = gates[vv] + gates[ww]
                        if ww in minimal_edge:
                            if vv not in minimal_edge:
                                minimal_edge[vv] = minimal_edge[ww]
                            elif minimal_edge[ww] < minimal_edge[vv]:
                                minimal_edge[vv] = minimal_edge[ww]

                        for t in G1.vertices():
                            if adjacent_vertex[t] == ww:
                                adjacent_vertex[t] = vv

            for v in G1.vertices():
                if v == adjacent_vertex[v]:
                    gates[v].sort()
                    i = 0

                    while i < len(gates[v]):
                        prefix = gates[v][i]
                        j = i + 1
                        while j < len(
                                gates[v]) and gates[v][j][0] == prefix[0]:
                            prefix = prefix[:G2.common_prefix_length(
                                gates[v][j], prefix)]
                            j += 1
                        if 2 * (j - i) > len(gates[v]) or (
                                2 * (j - i) == len(gates[v])
                                and edge_map[minimal_edge[v]][0] == prefix[0]):
                            if verbose:
                                print("Moving vertex", v, "towards", prefix)
                            for a in A1:
                                if v == adjacent_vertex[G1.initial_vertex(a)]:
                                    aa = A1.inverse_letter(a)
                                    edge_map[a] = G2.reduce_path(
                                        G2.reverse_path(prefix) * edge_map[a])
                                    edge_map[aa] = G2.reduce_path(
                                        edge_map[aa] * prefix)
                            if verbose:
                                out = ""
                                for a in A1.positive_letters():
                                    out += a + "->" + str(edge_map[a]) + ","
                                print(out[:-1])
                            done = False
                            break

                        i = j

                if not done:
                    break

        self.set_edge_map(edge_map)

        return self._edge_map

    def subdivide_domain(self, e):
        """
        Subdivide an edge in the domain graph.

        The edge ``e`` is subdivided into ``l`` edges where ``l`` is
        the length of the image of ``e`` by ``self``.

        Update the edge map of ``self``.

        Intended to be used by Stalling's folding algorithm to get an
        immersion.

        .. SEEALSO::

            :meth:`train_track.inverse_graph.GraphWithInverses.subdivide_edge()`

        INPUT:

        - ``e`` -- and edge of the domain of ``self``.

        OUTPUT:

        A dictionnary that maps old edges of the domain to new edges of the domain.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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: f.subdivide_domain('a')
            {'A': word: DCA, 'B': word: B, 'a': word: acd, 'b': word: b}
            sage: print(f)
            Graph map:
            a: 0->1, b: 0->0, c: 1->2, d: 2->0
            a: 0->0, b: 0->0
            edge map: a->a, b->ab, c->b, d->a
        """
        G = self.domain()
        A = G.alphabet()
        result_map = dict((a, Word([a])) for a in A)
        w = self.image(e)
        d = dict((a, self.image(a)) for a in A.positive_letters())
        new_edges = A.add_new_letters(len(w) - 1)
        new_vertices = G.new_vertices(len(w) - 1)
        for i, a in enumerate(new_edges):
            v = new_vertices[i]
            if i == 0:
                vi = G.initial_vertex(e)
                vt = G.terminal_vertex(e)
                f = new_edges[i][0]
                ee = A.inverse_letter(e)
                ff = new_edges[i][1]
                G.set_terminal_vertex(e, v)
                G.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]
            else:
                vi = self._domain.initial_vertex(new_edges[i - 1][0])
                vt = self._domain.terminal_vertex(new_edges[i - 1][0])
                f = new_edges[i][0]
                # ee=A.inverse_letter(e) #already done
                ff = new_edges[i][1]
                self._domain.set_terminal_vertex(new_edges[i - 1][0], v)
                self._domain.add_edge(v, vt, [f, ff])
                result_map[e] = result_map[e] * Word([f])
                result_map[ee] = Word([ff]) * result_map[ee]
                d[a[0]] = w[i + 1]

        # updating self.edge_map after subdivision
        if A.is_positive_letter(e):
            d[e] = Word([self.image(e)[0]])
        else:
            d[ee] = Word([self.image(ee)[-1]])

        self.set_edge_map(d)

        return result_map

    def illegal_turns(self, turns=None):
        """
        List of illegal turns in the domain graph.

        A turn is illegal if it is mapped by ``self`` to a degenerate
        turn.

        INPUT:

        - ``turns`` -- a list of turns of the domain graph. Default is None
          meaning all the turns of the graph. I not ``None`` return the
          sublist of ``turns`` consisting of illegal turns.


        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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: f.illegal_turns()
            [('a', 'b')]
        """
        result = []

        if turns is None:
            turns = self.domain().turns()

        for turn in turns:
            u = self.image(turn[0])
            v = self.image(turn[1])
            if len(u) == 0 or len(v) == 0 or u[0] == v[0]:
                result.append(turn)
        return result

    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``.

        The domain of ``self`` is fold until we get an
        immersion. Intended to be used to compute the pullback of two
        graph maps and the intersection of subgroupes of a free group.

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.

        Then fold one gate at one vertex and update the edge map and
        illegal turns list.

        Repeat the process till no illegal turns remain.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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: f.stallings_folding()
            sage: print(f)
            Graph map:
            a: 1->1, c: 1->1
            a: 0->0, b: 0->0
            edge map: a->a, c->b

        REFERENCES:

        .. [Stallings] J. Stallings, Topology of Finite Graphs,

        AUTHOR:

            - Radhika GUPTA

        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) > 1:
                self.subdivide_domain(a)

        Turns = self._domain.turns()
        # list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(Turns)
        # list of illegal turns in domain after subdivision
        counter = 0
        while len(Il_turns) > 0:
            counter = counter + 1

            # find edge_list (list of edges in the gate correspoding to e1)
            # to fold at exactly one vertex
            e1 = Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(a) == \
                        self._domain.initial_vertex(e1) and e1 != a:
                    if (e1, a) in Il_turns or (a, e1) in Il_turns:
                        edge_list.append(a)

            edge_list = list(set(edge_list))  # remove duplicates
            # fold at initial_vertex of e1 ( this function updates the
            # domain and edge_map)
            self._domain.fold(edge_list, [])

            # update edge_map again
            d = {}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)
            Turns = self._domain.turns()  # update list of all turns in domain
            Il_turns = self.illegal_turns(Turns)
            # update list of illegal turns in domain

    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 : G_3 \to 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 `G_2 \to G` with ``self`` (a
          graph map `G_1 \to G`)

        OUTPUT:

        A ``GraphMap``.

        EXAMPLES::

            sage: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            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:
            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)
            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)

    @staticmethod
    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: from train_track import *
            sage: from train_track.inverse_graph import GraphWithInverses
            sage: from train_track.graph_map import GraphMap
            sage: phi=FreeGroupAutomorphism('a->ab,b->ac,c->a')
            sage: print(GraphMap.rose_map(phi))
            Graph map:
            a: 0->0, b: 0->0, c: 0->0
            a: 0->0, b: 0->0, c: 0->0
            edge map: a->ab, b->ac, c->a
        """

        A = AlphabetWithInverses(automorphism.domain().variable_names())
        graph = GraphWithInverses.rose_graph(A)
        return GraphMap(
            graph, graph,
            automorphism.to_word_morphism(use_str=True,
                                          upper_case_as_inverse=True))
Ejemplo n.º 30
0
class GeoSub(SageObject):
    r"""
    INPUT:

    - ``sigma`` -- dict, substitution
    - ``k`` -- integer
    - ``presuf`` -- string (default: ``"prefix"``), ``"prefix"`` or ``"suffix"`` 
    - ``dual`` -- bool (default: ``False``)

    EXAMPLES::

        sage: from EkEkstar import GeoSub
        sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
        sage: E = GeoSub(sub, 2)
        sage: E
        E_2(1->12, 2->13, 3->1)
    """
    def __init__(self, sigma, k, presuf='prefix', dual=False):
        self._sigma_dict = sigma
        self._sigma = WordMorphism(sigma)
        self._k = k
        if not presuf in ['prefix', 'suffix']:
            raise ValueError('Input presuf(={}) should be "prefix" or'
                             ' "suffix"'.format(presuf))
        self._presuf = presuf
        self._dual = dual

    def is_dual(self):
        return self._dual

    @cached_method
    def field(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.field()
            Number Field in b with defining polynomial x^3 - x^2 - x - 1
        """
        M = self._sigma.incidence_matrix()
        b1 = max(M.eigenvalues(), key=abs)
        f = b1.minpoly()
        K = NumberField(f, 'b')
        return K

    def gen(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.gen()
            b^2 - b - 1
        """
        b = self.field().gen()
        if self.is_dual():
            return b
        else:
            return b**-1

    def dominant_left_eigenvector(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.dominant_left_eigenvector()
            (1, b - 1, b^2 - b - 1)
        """
        M = self._sigma.incidence_matrix() - self.field().gen()
        return M.left_kernel().basis()[0]

    def dominant_right_eigenvector(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.dominant_right_eigenvector()
            (1, b^2 - b - 1, -b^2 + 2*b)
        """
        M = self._sigma.incidence_matrix() - self.field().gen()
        return M.right_kernel().basis()[0]

    def complex_embeddings(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.complex_embeddings()
            [-0.419643377607081 - 0.606290729207199*I,
             -0.419643377607081 + 0.606290729207199*I,
             1.83928675521416]
        """
        return self.field().gen().complex_embeddings()

    def contracting_eigenvalues_indices(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.contracting_eigenvalues_indices()
            [0, 1]
        """
        L = self.complex_embeddings()
        return [L.index(x) for x in L if abs(x) < 1]

    def dilating_eigenvalues_indices(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.dilating_eigenvalues_indices()
            [2]
        """
        L = self.complex_embeddings()
        return [L.index(x) for x in L if abs(x) > 1]

    def minkowski_embedding_with_left_eigenvector(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.minkowski_embedding_with_left_eigenvector()
            [ -1.00000000000000 -0.839286755214161 -0.543689012692076]
            [ -1.00000000000000   1.41964337760708  0.771844506346038]
            [ 0.000000000000000 -0.606290729207199   1.11514250803994]

        ::

            sage: E = GeoSub(sub, 2, dual=True)
            sage: E.minkowski_embedding_with_left_eigenvector()
            [  1.00000000000000  0.839286755214161  0.543689012692076]
            [  1.00000000000000  -1.41964337760708 -0.771844506346038]
            [ 0.000000000000000  0.606290729207199  -1.11514250803994]

        """
        K = self.field()
        if self.is_dual():
            vb = self.dominant_left_eigenvector()
        else:
            vb = -self.dominant_left_eigenvector()
        return Minkowski_embedding_without_sqrt2(K, vb)

    def projection(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: GeoSub(sub, 2).projection()
            [ -1.000000000000000000000000000000
              -0.8392867552141611325518525646713
              -0.5436890126920763615708559718500]
            sage: GeoSub(sub, 2, dual=True).projection()
            [  1.00000000000000  -1.41964337760708 -0.771844506346038]
            [ 0.000000000000000  0.606290729207199  -1.11514250803994]

        """
        K = self.field()
        vb = self.dominant_left_eigenvector()
        P, Q = Minkowski_projection_pair(K, vb)
        if self.is_dual():
            return Q
        else:
            return -P

    @cached_method
    def base_iter(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.base_iter()
            {(1, 2): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (2,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (1, 3)],
            [(1, 0, 0), (2, 1)],
            [(2, 0, 0), (2, 3)]],
            (1, 3): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (2,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (2, 1)]],
            (2, 3): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (3,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (3, 1)]]}
        """
        X = {}
        S = self._sigma_dict.keys()
        for x in combinations(S, self._k):
            X[x] = []
            bigL = []
            for y in x:
                if self.is_dual():
                    bigL.append(
                        ps_automaton_inverted(self._sigma_dict,
                                              self._presuf)[y])
                else:
                    bigL.append(
                        ps_automaton(self._sigma_dict, self._presuf)[y])
                Lpro = list(product(*bigL))
                for el in Lpro:
                    z = []
                    w = []
                    for i in range(len(el)):
                        z += el[i][1]
                        w.append(el[i][0])
                    if self.is_dual():
                        M = self._sigma.incidence_matrix()
                        X[x].append([-M.inverse() * abelian(z, S), tuple(w)])
                    else:
                        X[x].append([abelian(z, S), tuple(w)])
        return X

    def __call__(self, patch, iterations=1):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub, kPatch, kFace
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, 'prefix',1)
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True),
            ....:             kFace((0,1,0),(2,1),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: Q = geosub(P, 6)
            sage: Q
            Patch of 47 faces
        """
        if iterations == 0:
            return kPatch(patch)
        elif iterations < 0:
            raise ValueError("iterations (=%s) must be >= 0." % iterations)
        else:
            old_faces = patch
            for i in range(iterations):
                new_faces = kPatch([])
                for f, m in old_faces:
                    new_faces += m * kPatch(
                        self._call_on_face(f, color=f.color()))
                old_faces = new_faces
            return new_faces

    def matrix(self):
        r"""
        EXAMPLES::

            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.matrix()
            [1 1 1]
            [1 0 0]
            [0 1 0]
        """
        if self.is_dual():
            return self._sigma.incidence_matrix().inverse()
        else:
            return self._sigma.incidence_matrix()

    def _call_on_face(self, face, color=None):
        r"""
        INPUT:

        - ``face`` -- a face
        - ``color`` -- a color or None

        OUTPUT:

            dict of the form ``{face:multiplicity for face in faces}``

        EXAMPLES::
            
            sage: from EkEkstar import GeoSub, kFace
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)

        The available face type are::

            sage: E.base_iter().keys()
            [(1, 2), (1, 3), (2, 3)]

        For each we get::

            sage: d = E._call_on_face(kFace((10,11,12), (1,2)))
            sage: sorted(d.items())
            [([(34, 10, 11), (1, 3)], 1),
             ([(33, 10, 11), (1, 1)], 1),
             ([(34, 10, 11), (2, 1)], 1),
             ([(35, 10, 11), (2, 3)], 1)]
            sage: sorted(E._call_on_face(kFace((10,11,12), (1,3))).items())
            [([(34, 10, 11), (2, 1)], 1), ([(33, 10, 11), (1, 1)], 1)]
            sage: E._call_on_face(kFace((10,11,12), (2,3)))
            {[(33, 10, 11), (1, 1)]: 1, [(34, 10, 11), (3, 1)]: 1}

        """
        x_new = self.matrix() * face.vector()
        #t = face.type()
        t = face.sorted_type()
        if self.is_dual():
            return {
                kFace(x_new - vv, tt, dual=self.is_dual()):
                (-1)**(sum(t) + sum(tt)) * face.sign()
                for (vv, tt) in self.base_iter()[t] if len(tt) == self._k
            }
        else:
            return {
                kFace(x_new + vv, tt, dual=self.is_dual()): face.sign()
                for (vv, tt) in self.base_iter()[t] if len(tt) == self._k
            }

    def __repr__(self):
        r"""
        EXAMPLES::
            
            sage: from EkEkstar import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: GeoSub(sub, 2)
            E_2(1->12, 2->13, 3->1)
            sage: GeoSub(sub, 2, dual=True)
            E*_2(1->12, 2->13, 3->1)
        """
        if self.is_dual():
            return "E*_%s(%s)" % (self._k, str(self._sigma))
        else:
            return "E_%s(%s)" % (self._k, str(self._sigma))
Ejemplo n.º 31
0
class GraphMap():
    """
    A GraphMap is a map from a Graph to another .  It maps a vertex to
    a vertex and an edge to an edge-path. It respects incidence
    relation. The inverse of an edge is send to the reverse path.

    AUTHORS:

    - Thierry Coulbois (2013-05-16): beta.0 version
    """

    def __init__(self,domain,codomain,edge_map,vertex_map=None):
        self._domain=domain
        self._codomain=codomain
        self.set_edge_map(edge_map)
        self._vertex_map=vertex_map

    def __call__(self,argument):
        """
        Applies ``self`` to ``argument`` which is either a vertex of ``self`` or
        an edge path.

        SEE ALSO:

        To compute the image of a letter of the alphabet use
        ``self.image(a)``.
        """
        if self._domain.has_vertex(argument):
            if self._vertex_map==None:
                self.update_vertex_map()

            return self._vertex_map[argument]
        else:
            return self._codomain.reduce_path(self._edge_map(argument))

    def __mul__(self,other):
        """
        Compose ``self`` with ``other``.
        """
        A=other._domain.alphabet()
        result_map={}
        for a in A.positive_letters():
            result_map[a]=self(other._edge_map.image(a))
        return GraphMap(other._domain,self._codomain,result_map)


    def __str__(self):
        """
        String represetation of ``self``.
        """
        result="Graph map:\n"+self._domain.__str__()+"\n"
        result+=self._codomain.__str__()+"\n"
        result=result+"edge map: "
        for a in self._domain._alphabet.positive_letters(): result+=a+"->"+self.image(a).__str__()+", "
        result=result[:-2]+"\n"
        if self._vertex_map!=None:
            result=result+"vertex map: "+self._vertex_map.__str__()+"\n"
        return result

    def domain(self):
        """
        Domain of ``self``: this is a graph.
        """
        return self._domain

    def codomain(self):
        """
        Codomain of ``self``: this is a graph.
        """
        return self._codomain

    def set_edge_map(self,edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``Wordmorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        """
        A=self.domain().alphabet()
        tmp_map=WordMorphism(edge_map)
        m={}
        for a in tmp_map._domain.alphabet():
            m[a]=self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)]=self._codomain.reverse_path(m[a])
        self._edge_map=WordMorphism(m)
        self._vertex_map=None

    def compose_edge_map(self,edge_morph):
        """
        Compose ``self`` with the morphism ``edge_morph``.

        Update the edge_map of ``self`` with (``edge_morph`` o ``self``).
        """
        edge_map=dict((a,edge_morph(self._edge_map.image(a))) for a in self._domain._alphabet.positive_letters())
        self.set_edge_map(edge_map)

    def update_vertex_map(self):
        """
        Computes the vertex map of ``self`` from its edge map.
        """
        vertex_map={}
        for e in self._domain._alphabet.positive_letters():
            p=self.image(e)
            if len(p)>0:
                vertex_map[self._domain.initial_vertex(e)]=self._codomain.initial_vertex(p[0])
                vertex_map[self._domain.terminal_vertex(e)]=self._codomain.terminal_vertex(p[-1])
        self._vertex_map=vertex_map

    def edge_map(self):
        """
        The edge map of ``self``: this is a word morphism.
        """
        return self._edge_map

    def image(self,letter,iter=1):
        """
        The image of a letter.

        if ``iter>1`` then returns ``self^iter(letter)``
        """

        if iter==1:
            return self._edge_map.image(letter)
        else:
            return self._codomain.reduce_path(self._edge_map(self._edge_map(letter),iter-1))

    def inverse(self):
        """
        A homotopy inverse of ``self``.

        WARNING:

        ``self`` is assumed to be a homotopy equivalence.
        """


        G1=self.domain()
        A1=G1.alphabet()
        t1=G1.spanning_tree()

        G2=self.codomain()
        A2=G2.alphabet()
        t2=G2.spanning_tree()

        A=AlphabetWithInverses(len(A1)-len(G1.vertices())+1)
        F=FreeGroup(A)

        map=dict()
        translate=dict()

        i=0
        for a in A1.positive_letters():
            l=len(t1[G1.initial_vertex(a)])-len(t1[G1.terminal_vertex(a)])
            if (l!=1 or t1[G1.initial_vertex(a)][-1]!=A1.inverse_letter(a)) and\
                    (l!=-1 or t1[G1.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                map[A[i]]=self(t1[G1.initial_vertex(a)]*Word([a])*G1.reverse_path(t1[G1.terminal_vertex(a)]))
                translate[A[i]]=a
                translate[A.inverse_letter(A[i])]=A1.inverse_letter(a)
                i+=1

        rename=dict()
        edge_map=dict()

        i=0
        for a in A2.positive_letters():
            l=len(t2[G2.initial_vertex(a)])-len(t2[G2.terminal_vertex(a)])
            if (l!=1 or t2[G2.initial_vertex(a)][-1]!=A2.inverse_letter(a)) and\
                    (l!=-1 or t2[G2.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                rename[a]=A[i]
                rename[A2.inverse_letter(a)]=A.inverse_letter(A[i])
                i+=1
            else:
                edge_map[a]=Word()

        for a in map:
            map[a]=F([rename[b] for b in map[a] if b in rename])

        phi=FreeGroupAutomorphism(map,F)
        psi=phi.inverse()

        i=0
        for a in A2.positive_letters():
            if a not in edge_map:
                result=Word()
                for b in psi.image(A[i]):
                    c=translate[b]
                    result=result*t1[G1.initial_vertex(c)]*Word([c])*G1.reverse_path(t1[G1.terminal_vertex(c)])
                edge_map[a]=G1.reduce_path(result)
                i+=1

        return GraphMap(G2,G1,edge_map)


    def tighten(self):
        """
        Tighten ``self`` such that there are at least two gates at
        each vertex of the domain.

        A map is tight if for each vertex ``v`` of the domain, there
        exist reduced edge paths ``u`` and ``v`` in the domain with
        ``self(u)`` and ``self(v)`` non-trivial reduced paths starting
        with different edges.

        ``self`` and ``self.tighten()`` are homotopic.

        """
        G1=self.domain()
        A1=G1.alphabet()
        G2=self.domain()

        edge_map=dict((a,self.image(a)) for a in A1)

        done=False
        while not done:
            done=True
            prefix=dict()
            for a in A1:
                u=edge_map[a]
                v=G1.initial_vertex(a)
                if len(u)>0:
                    if v in prefix:
                        if len(prefix[v])>0:
                            p=G2.common_prefix_length(u,prefix[v])
                            prefix[v]=prefix[v][:p]
                    else:
                        prefix[v]=u

            for a in A1:
                v=G1.initial_vertex(a)
                if v in prefix and len(prefix[v])>0:
                    done=False
                    aa=A1.inverse_letter(a)
                    if len(edge_map[a])>0:
                        edge_map[a]=edge_map[a][len(prefix[v]):]
                        edge_map[aa]=edge_map[aa][:-len(prefix[v])]
                    else:
                        edge_map[a]=G2.reverse_path(prefix[v])
                        edge_map[aa]=prefix[v]

        self.set_edge_map(edge_map)

        return self

    @staticmethod
    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``.
        """

        graph=GraphWithInverses.rose_graph(automorphism.domain().alphabet().copy())
        return GraphMap(graph,graph,automorphism)
Ejemplo n.º 32
0
    def iter_morphisms(self, arg=None, codomain=None, min_length=1):
        r"""
        Iterate over all morphisms with domain ``self`` and the given
        codomain.

        INPUT:

        - ``arg`` - (optional, default: None) It can be one of the following :

          - ``None`` - then the method iterates through all morphisms.

          - tuple `(a, b)` of two integers  - It specifies the range
            ``range(a, b)`` of values to consider for the sum of the length
            of the image of each letter in the alphabet.

          - list of nonnegative integers - The length of the list must be
            equal to the size of the alphabet, and the i-th integer of
            ``arg`` determines the length of the word mapped to by the i-th
            letter of the (ordered) alphabet.

        - ``codomain`` - (default: None) a combinatorial class of words.
          By default, ``codomain`` is ``self``. 

        - ``min_length`` - (default: 1) nonnegative integer. If ``arg`` is
          not specified, then iterate through all the morphisms where the
          length of the images of each letter in the alphabet is at least
          ``min_length``. This is ignored if ``arg`` is a list.
          
        OUTPUT:

        iterator
            
        EXAMPLES:

        Iterator over all non-erasing morphisms::
        
            sage: W = Words('ab')
            sage: it = W.iter_morphisms()
            sage: for _ in range(7): it.next()
            WordMorphism: a->a, b->a
            WordMorphism: a->a, b->b
            WordMorphism: a->b, b->a
            WordMorphism: a->b, b->b
            WordMorphism: a->aa, b->a
            WordMorphism: a->aa, b->b
            WordMorphism: a->ab, b->a

        Iterator over all morphisms including erasing morphisms::
        
            sage: W = Words('ab')
            sage: it = W.iter_morphisms(min_length=0)
            sage: for _ in range(7): it.next()
            WordMorphism: a->, b->
            WordMorphism: a->a, b->
            WordMorphism: a->b, b->
            WordMorphism: a->, b->a
            WordMorphism: a->, b->b
            WordMorphism: a->aa, b->
            WordMorphism: a->ab, b->

        Iterator over morphisms where the sum of the lengths of the images
        of the letters is in a specific range::

            sage: for m in W.iter_morphisms((0, 3), min_length=0): m
            WordMorphism: a->, b->
            WordMorphism: a->a, b->
            WordMorphism: a->b, b->
            WordMorphism: a->, b->a
            WordMorphism: a->, b->b
            WordMorphism: a->aa, b->
            WordMorphism: a->ab, b->
            WordMorphism: a->ba, b->
            WordMorphism: a->bb, b->
            WordMorphism: a->a, b->a
            WordMorphism: a->a, b->b
            WordMorphism: a->b, b->a
            WordMorphism: a->b, b->b
            WordMorphism: a->, b->aa
            WordMorphism: a->, b->ab
            WordMorphism: a->, b->ba
            WordMorphism: a->, b->bb

        ::

            sage: for m in W.iter_morphisms( (2, 4) ): m
            WordMorphism: a->a, b->a
            WordMorphism: a->a, b->b
            WordMorphism: a->b, b->a
            WordMorphism: a->b, b->b
            WordMorphism: a->aa, b->a
            WordMorphism: a->aa, b->b
            WordMorphism: a->ab, b->a
            WordMorphism: a->ab, b->b
            WordMorphism: a->ba, b->a
            WordMorphism: a->ba, b->b
            WordMorphism: a->bb, b->a
            WordMorphism: a->bb, b->b
            WordMorphism: a->a, b->aa
            WordMorphism: a->a, b->ab
            WordMorphism: a->a, b->ba
            WordMorphism: a->a, b->bb
            WordMorphism: a->b, b->aa
            WordMorphism: a->b, b->ab
            WordMorphism: a->b, b->ba
            WordMorphism: a->b, b->bb

        Iterator over morphisms with specific image lengths::

            sage: for m in W.iter_morphisms([0, 0]): m
            WordMorphism: a->, b->
            sage: for m in W.iter_morphisms([0, 1]): m
            WordMorphism: a->, b->a
            WordMorphism: a->, b->b
            sage: for m in W.iter_morphisms([2, 1]): m
            WordMorphism: a->aa, b->a
            WordMorphism: a->aa, b->b
            WordMorphism: a->ab, b->a
            WordMorphism: a->ab, b->b
            WordMorphism: a->ba, b->a
            WordMorphism: a->ba, b->b
            WordMorphism: a->bb, b->a
            WordMorphism: a->bb, b->b
            sage: for m in W.iter_morphisms([2, 2]): m
            WordMorphism: a->aa, b->aa
            WordMorphism: a->aa, b->ab
            WordMorphism: a->aa, b->ba
            WordMorphism: a->aa, b->bb
            WordMorphism: a->ab, b->aa
            WordMorphism: a->ab, b->ab
            WordMorphism: a->ab, b->ba
            WordMorphism: a->ab, b->bb
            WordMorphism: a->ba, b->aa
            WordMorphism: a->ba, b->ab
            WordMorphism: a->ba, b->ba
            WordMorphism: a->ba, b->bb
            WordMorphism: a->bb, b->aa
            WordMorphism: a->bb, b->ab
            WordMorphism: a->bb, b->ba
            WordMorphism: a->bb, b->bb
            
        The codomain may be specified as well::

            sage: Y = Words('xyz')
            sage: for m in W.iter_morphisms([0, 2], codomain=Y): m
            WordMorphism: a->, b->xx
            WordMorphism: a->, b->xy
            WordMorphism: a->, b->xz
            WordMorphism: a->, b->yx
            WordMorphism: a->, b->yy
            WordMorphism: a->, b->yz
            WordMorphism: a->, b->zx
            WordMorphism: a->, b->zy
            WordMorphism: a->, b->zz
            sage: for m in Y.iter_morphisms([0,2,1], codomain=W): m
            WordMorphism: x->, y->aa, z->a
            WordMorphism: x->, y->aa, z->b
            WordMorphism: x->, y->ab, z->a
            WordMorphism: x->, y->ab, z->b
            WordMorphism: x->, y->ba, z->a
            WordMorphism: x->, y->ba, z->b
            WordMorphism: x->, y->bb, z->a
            WordMorphism: x->, y->bb, z->b
            sage: it = W.iter_morphisms(codomain=Y)
            sage: for _ in range(10): it.next()
            WordMorphism: a->x, b->x
            WordMorphism: a->x, b->y
            WordMorphism: a->x, b->z
            WordMorphism: a->y, b->x
            WordMorphism: a->y, b->y
            WordMorphism: a->y, b->z
            WordMorphism: a->z, b->x
            WordMorphism: a->z, b->y
            WordMorphism: a->z, b->z
            WordMorphism: a->xx, b->x

        TESTS::

            sage: list(W.iter_morphisms([1,0]))
            [WordMorphism: a->a, b->, WordMorphism: a->b, b->]
            sage: list(W.iter_morphisms([0,0], codomain=Y))
            [WordMorphism: a->, b->]
            sage: list(W.iter_morphisms([0, 1, 2]))
            Traceback (most recent call last):
            ...
            TypeError: arg (=[0, 1, 2]) must be an iterable of 2 integers
            sage: list(W.iter_morphisms([0, 'a']))
            Traceback (most recent call last):
            ...
            TypeError: arg (=[0, 'a']) must be an iterable of 2 integers
            sage: list(W.iter_morphisms([0, 1], codomain='a'))
            Traceback (most recent call last):
            ...
            TypeError: codomain (=a) must be an instance of Words_over_OrderedAlphabet

        The argument ``l`` is now deprecated::

            sage: W = Words('ab')
            sage: it = W.iter_morphisms(l=None)
            doctest:...: DeprecationWarning: use the option 'arg' instead of 'l'
            See http://trac.sagemath.org/10134 for details.
        """
        n = self.size_of_alphabet()
        # create an iterable of compositions (all "compositions" if arg is
        # None, or [arg] otherwise)
        if arg is None:
            from sage.combinat.integer_list import IntegerListsLex
            compositions = IntegerListsLex(itertools.count(),
                                           length=n,
                                           min_part=max(0, min_length))
        elif isinstance(arg, tuple):
            if not len(arg) == 2 or not all(
                    isinstance(a, (int, Integer)) for a in arg):
                raise TypeError("arg (=%s) must be a tuple of 2 integers" %
                                arg)
            from sage.combinat.integer_list import IntegerListsLex
            compositions = IntegerListsLex(range(*arg),
                                           length=n,
                                           min_part=max(0, min_length))
        else:
            arg = list(arg)
            if (not len(arg) == n
                    or not all(isinstance(a, (int, Integer)) for a in arg)):
                raise TypeError(
                    "arg (=%s) must be an iterable of %s integers" % (arg, n))
            compositions = [arg]

        # set the codomain
        if codomain is None:
            codomain = self
        elif not isinstance(codomain, Words_over_OrderedAlphabet):
            raise TypeError, "codomain (=%s) must be an instance of Words_over_OrderedAlphabet" % codomain

        # iterate through the morphisms
        from sage.combinat.words.morphism import WordMorphism
        for composition in compositions:
            cuts = [0] + list(composition)
            for i in range(1, len(cuts)):
                cuts[i] += cuts[i - 1]
            s = cuts[-1]  # same but better than s = sum(composition)
            for big_word in codomain.iterate_by_length(s):
                d = {}
                i = 0
                for a in self.alphabet():
                    d[a] = big_word[cuts[i]:cuts[i + 1]]
                    i += 1
                yield WordMorphism(d, codomain=codomain)
Ejemplo n.º 33
0
class GraphMap():
    """
    A GraphMap is a map from a Graph to another.  It maps a vertex to
    a vertex and an edge to an edge-path. It respects incidence
    relation. The inverse of an edge is send to the reverse path.

    AUTHORS:

    - Thierry Coulbois (2013-05-16): beta.0 version
    """

    def __init__(self,*args):
        """
        The following forms are accepted:

        - ``GraphMap(f)`` where ``f`` is a ``GraphMap``.

        - ``GraphMap(domain,codomain,edge_map,vertex_map=None)`` where
          ``domain`` and ``codomain`` are ``GraphWithInverses`` and
          ``edge_map`` is anything accepted by
          ``WordMorphism(edge_map)`` with domain alphabet an
          AlphabetWithInverses (note that only one image of the pair
          (a,inverse_letter(a)) needs to be defined for each letter).
        """
        if isinstance(args[0],GraphMap):
            self._domain=args[0]._domain
            self._codomain=args[0]._codomain
            self._edge_map=args[0]._edge_map
            self._vertex_map=args[0]._vertex_map
        else:
            self._domain=args[0]
            self._codomain=args[1]
            self.set_edge_map(args[2])
            if len(args)>3:
                self._vertex_map=args[3]
        

    def __call__(self,argument):
        """
        Applies ``self`` to ``argument`` which is either a vertex of ``self`` or
        an edge path.

        SEE ALSO:

        To compute the image of a letter of the alphabet use
        ``self.image(a)``.
        """
        if self._domain.has_vertex(argument):
            if self._vertex_map==None:
                self.update_vertex_map()

            return self._vertex_map[argument]
        else:
            return self._codomain.reduce_path(self._edge_map(argument))

    def __mul__(self,other):
        """
        Compose ``self`` with ``other``.
        """
        A=other._domain.alphabet()
        result_map={}
        for a in A.positive_letters():
            result_map[a]=self(other._edge_map.image(a))
        return GraphMap(other._domain,self._codomain,result_map)


    def __str__(self):
        """
        String represetation of ``self``.
        """
        result="Graph map:\n"+self._domain.__str__()+"\n"
        result+=self._codomain.__str__()+"\n"
        result=result+"edge map: "
        for a in self._domain._alphabet.positive_letters(): result+=a+"->"+self.image(a).__str__()+", "
        result=result[:-2]+"\n"
        if self._vertex_map!=None:
            result=result+"vertex map: "+self._vertex_map.__str__()+"\n"
        return result

    def domain(self):
        """
        Domain of ``self``: this is a graph.
        """
        return self._domain

    def codomain(self):
        """
        Codomain of ``self``: this is a graph.
        """
        return self._codomain

    def set_edge_map(self,edge_map):
        """
        Sets the edge map of ``self``.

        ``edge_map`` is anything that is accepted by
        ``Wordmorphism(edge_map)``, the image of the inverse letters
        will be calculated: they need not be explicit in ``edge_map``,
        only one of the images of each pair [letter,inverse(letter)]
        need to be given by ``edge_map``. Images of ``edge_map`` need
        not be reduced.

        """
        A=self.domain().alphabet()
        tmp_map=WordMorphism(edge_map)
        m={}
        for a in tmp_map._domain.alphabet():
            m[a]=self._codomain.reduce_path(tmp_map.image(a))
            m[A.inverse_letter(a)]=self._codomain.reverse_path(m[a])
        self._edge_map=WordMorphism(m)
        self._vertex_map=None

    def compose_edge_map(self,edge_morph):
        """
        Compose ``self`` with the morphism ``edge_morph``.

        Update the edge_map of ``self`` with (``edge_morph`` o ``self``).
        """
        edge_map=dict((a,edge_morph(self._edge_map.image(a))) for a in self._domain._alphabet.positive_letters())
        self.set_edge_map(edge_map)

    def update_vertex_map(self):
        """
        Computes the vertex map of ``self`` from its edge map.
        """
        vertex_map={}
        for e in self._domain._alphabet.positive_letters():
            p=self.image(e)
            if len(p)>0:
                vertex_map[self._domain.initial_vertex(e)]=self._codomain.initial_vertex(p[0])
                vertex_map[self._domain.terminal_vertex(e)]=self._codomain.terminal_vertex(p[-1])
        self._vertex_map=vertex_map

    def edge_map(self):
        """
        The edge map of ``self``: this is a word morphism.
        """
        return self._edge_map

    def image(self,letter,iter=1):
        """
        The image of a letter.

        if ``iter>1`` then returns ``self^iter(letter)``
        """

        if iter==1:
            return self._edge_map.image(letter)
        else:
            return self._codomain.reduce_path(self._edge_map(self._edge_map(letter),iter-1))

    def inverse(self):
        """A homotopy inverse of ``self``.

        For ``t1=self.domain().spanning_tree()`` and
        ``t2=self.codomain().spanning_tree()``. The alphabet ``A`` is
        made of edges not in ``t1`` identified (by their order in the
        alphabets of the domain and codomain) to letters not in
        ``t2``. The automorphism ``phi`` of ``FreeGroup(A)`` is
        defined using ``t1`` and ``t2``. The inverse map is given by
        ``phi.inverse()`` using ``t1`` and edges from ``t2`` are
        mapped to a single point (the root of ``t1``).

        In particular the inverse maps all vertices to the root of ``t1``.

        WARNING:

        ``self`` is assumed to be a homotopy equivalence.

        """

        from free_group import FreeGroup
        from free_group_automorphism import FreeGroupAutomorphism

        G1=self.domain()
        A1=G1.alphabet()
        t1=G1.spanning_tree()

        G2=self.codomain()
        A2=G2.alphabet()
        t2=G2.spanning_tree()

        A=AlphabetWithInverses(len(A1)-len(G1.vertices())+1)
        F=FreeGroup(A)

        map=dict()
        translate=dict()

        i=0
        for a in A1.positive_letters():
            l=len(t1[G1.initial_vertex(a)])-len(t1[G1.terminal_vertex(a)])
            if (l!=1 or t1[G1.initial_vertex(a)][-1]!=A1.inverse_letter(a)) and\
                    (l!=-1 or t1[G1.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                map[A[i]]=self(t1[G1.initial_vertex(a)]*Word([a])*G1.reverse_path(t1[G1.terminal_vertex(a)]))
                translate[A[i]]=a
                translate[A.inverse_letter(A[i])]=A1.inverse_letter(a)
                i+=1

        rename=dict()
        edge_map=dict()

        i=0
        for a in A2.positive_letters():
            l=len(t2[G2.initial_vertex(a)])-len(t2[G2.terminal_vertex(a)])
            if (l!=1 or t2[G2.initial_vertex(a)][-1]!=A2.inverse_letter(a)) and\
                    (l!=-1 or t2[G2.terminal_vertex(a)][-1]!=a): # a is not in the spanning tree
                rename[a]=A[i]
                rename[A2.inverse_letter(a)]=A.inverse_letter(A[i])
                i+=1
            else:
                edge_map[a]=Word()

        for a in map:
            map[a]=F([rename[b] for b in map[a] if b in rename])

        phi=FreeGroupAutomorphism(map,F)
        psi=phi.inverse()

        i=0
        for a in A2.positive_letters():
            if a not in edge_map:
                result=Word()
                for b in psi.image(A[i]):
                    c=translate[b]
                    result=result*t1[G1.initial_vertex(c)]*Word([c])*G1.reverse_path(t1[G1.terminal_vertex(c)])
                edge_map[a]=G1.reduce_path(result)
                i+=1

        return GraphMap(G2,G1,edge_map)


    def tighten(self):
        """
        Tighten ``self`` such that there are at least two gates at
        each vertex of the domain.

        A map is tight if for each vertex ``v`` of the domain, there
        exist reduced edge paths ``u`` and ``v`` in the domain with
        ``self(u)`` and ``self(v)`` non-trivial reduced paths starting
        with different edges.

        ``self`` and ``self.tighten()`` are homotopic.

        WARNING:

        It is assumed that ``self`` is a homotopy equivalence

        The result may send edges to trivial edge-paths.

        """
        G1=self.domain()
        A1=G1.alphabet()
        G2=self.domain()

        edge_map=dict((a,self.image(a)) for a in A1)

        done=False
        while not done:
            done=True
            prefix=dict() # the common prefix of all edges outgoing from the class of a vertex
            adjacent_vertex=dict() # a class of vertices linked by a tree which is contracted by self to a point
            for a in A1:
                u=edge_map[a]
                v=G1.initial_vertex(a)
                if len(u)>0:
                    if v not in adjacent_vertex:
                        adjacent_vertex[v]=set([v])
                    for w in adjacent_vertex[v]:
                        if w in prefix:
                            if len(prefix[w])>0:
                                p=G2.common_prefix_length(u,prefix[w])
                                prefix[w]=prefix[w][:p]
                            else:
                                prefix[w]=u
                else: #we need to increase the adjacent_vertex
                    vv=G1.terminal_vertex(a) #note that v!=vv because else the loop a is contracted contrary to homotopy equivalence
                    if v in adjacent_vertex and vv in adjacent_vertex:
                        adjacent_vertex[v].update(adjacent_vertex[vv])
                    elif v in adjacent_vertex:
                        adjacent_vertex[v].add(vv)
                    elif vv in adjacent_vertex:
                        adjacent_vertex[v]=adjacent_vertex[vv]
                        adjacent_vertex[v].add(v)
                    else:
                        adjacent_vertex[v]=set([v,vv])
                    if v in prefix:
                        prefixv=prefix[v]
                    else:
                        prefixv=False
                    for w in adjacent_vertex[v]:
                        if v!=w:
                            adjacent_vertex[w]=adjacent_vertex[v]
                            if w in prefix:
                                if prefixv:
                                    p=G2.common_prefix_length(prefixv,prefix[w])
                                    prefixv=prefixv[:p]
                                else:
                                    prefixv=prefix[w]
                    if prefixv:
                        for w in adjacent_vertex[v]:
                            prefix[w]=prefixv

            for a in A1:
                v=G1.initial_vertex(a)
                if v in prefix and len(prefix[v])>0:
                    done=False
                    aa=A1.inverse_letter(a)
                    if len(edge_map[a])>0:
                        edge_map[a]=edge_map[a][len(prefix[v]):]
                        edge_map[aa]=edge_map[aa][:-len(prefix[v])]
                    # else:
                    #     edge_map[a]=G2.reverse_path(prefix[v])
                    #     edge_map[aa]=prefix[v]
                    break

        self.set_edge_map(edge_map)

        return self


    def subdivide_domain(self, e):
        """
        Subdivide an edge in the domain graph. 

        The edge ``e`` is subdivided into ``l`` edges where ``l`` is
        the length of the image of ``e`` by ``self``.

        Update the edge map of ``self``.

        Intended to be used by Stalling's folding algorithm to get an
        immersion.

        SEE ALSO::

        GraphWithInverses.subdivide_edge()

        """
        G=self.domain()
        A = G.alphabet()
        result_map=dict((a,Word([a])) for a in A)
        w = self.image(e)
        new_edges=A.add_new_letters(len(w)-1)
        new_vertices=G.new_vertices(len(w)-1)
        d={(a,self.image(a)) for a in A.positive_letters()}
        for i,a in enumerate(new_edges):
            v=new_vertices[i]
            if i==0:
                vi=G.initial_vertex(e)
                vt=G.terminal_vertex(e)
                f=new_edges[i][0]
                ee=A.inverse_letter(e)
                ff=new_edges[i][1]
                G.set_terminal_vertex(e,v)
                G.add_edge(v,vt,[f,ff])
                result_map[e]=result_map[e]*Word([f])
                result_map[ee]=Word([ff])*result_map[ee]
                d[a[0]] = w[i+1]
            else:
                vi=self._domain.initial_vertex(new_edges[i-1][0])
                vt=self._domain.terminal_vertex(new_edges[i-1][0])
                f=new_edges[i][0]
                #ee=A.inverse_letter(e) #already done
                ff=new_edges[i][1]
                self._domain.set_terminal_vertex(new_edges[i-1][0],v)
                self._domain.add_edge(v,vt,[f,ff])
                result_map[e]=result_map[e]*Word([f])
                result_map[ee]=Word([ff])*result_map[ee]
                d[a[0]] = w[i+1]
                    
        # updating self.edge_map after subdivision
        if A.is_positive_letter(e):
            d[e] = Word([self.image(e)[0]])
        else:
            d[ee]=Word([self.image(ee)[-1]])

        self.set_edge_map(d)
                                   
        return result_map
        
    def illegal_turns(self,turns=None):
        """
        List of illegal turns in the domain graph.

        A turn is illegal if it is mapped by ``self`` to a degenerate
        turn.

        INPUT:

        ``turns`` a list of turns of the domain graph. Default is None
        meaning all the turns of the graph. I not ``None`` return the
        sublist of ``turns`` consisting of illegal turns.
        """
        result = []

        if turns is None:
            turns=self.domain().turns()

        for turn in turns:
            u=self.image(turn[0])
            v=self.image(turn[1])
            if len(u)==0 or len(v)==0 or u[0]==v[0]:
                result.append(turn)
        return result
        

    def stallings_folding(self):
        """
        Implement Stallings' folding to get an immersion from ``self``. 

        The domain of ``self`` is fold until we get an immersion. 

        ALGORITHM:

        We first subdivide edges of the domain according to length of
        their image.  

        Then fold one gate at one vertex and update the edge map and
        illegal turns list. 

        Repeat the process till no illegal turns remain.

        REFERENCES:

        [Stallings] J. Stallings, Topology of Finite Graphs,
        """
        A = self.domain().alphabet()
        for a in A:
            if len(self.image(a)) >1:
                self.subdivide_domain(a)
                
        Turns = self._domain.turns() #list of all turns in domain after subdivision
        Il_turns = self.illegal_turns(Turns) # list of illegal turns in domain after subdivision    
        counter = 0
        while len(Il_turns)>0:
            counter = counter +1
            
            # find edge_list (list of edges in the gate correspoding to e1) to fold at exactly one vertex
            e1=Il_turns[0][0]
            edge_list = [e1]
            for a in A:
                if self._domain.initial_vertex(a) == self._domain.initial_vertex(e1) and e1!=a:
                    if (e1,a) in Il_turns or (a,e1) in Il_turns:
                        edge_list.append(a) 
                        
            edge_list = list(set(edge_list)) # remove duplicates 
            # fold at initial_vertex of e1 ( this function updates the domain and edge_map)         
            self._domain.fold(edge_list,[])
                
            #update edge_map again 
            d={}
            d[edge_list[0]] = self.image(edge_list[0])
            for a in A:
                if a not in edge_list:
                    d[a] = self.image(a)
            wm = WordMorphism(d)
            self.set_edge_map(wm)    
            Turns = self._domain.turns() #update list of all turns in domain 
            Il_turns = self.illegal_turns(Turns) # update list of illegal turns in domain 
                 
        
        return self
        
        
    def pullback(self,f2,G3,A):
        """
        INPUT : Two Graph maps f1:G1->G, f2:G2->G, an empty GraphWithInverses G3 and an empty AlphabetWithInverses A
        OPERATION : Find the pullback G3 and a graph map f:G3->G
        OUTPUT : Graphmap f 
        
        The pullback method can be used to find intersection of two subgroups of a Free Group. 
        
        Example : 
        G1 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='x0')) 
        G2 = GraphWithInverses.rose_graph(AlphabetWithInverses(2,type='a0')) 
        G =  GraphWithInverses.rose_graph(AlphabetWithInverses(2)) 
        n1 = WordMorphism({'x0':['a','a'],'x1':['b','a']})
        n2 = WordMorphism({'a0':['b','a'],'a1':['b','b','b','a','B','a']})
        f1 = GraphMap(G1,G,n1)
        f2 = GraphMap(G2,G,n2)
        G3 = GraphWithInverses()
        A = AlphabetWithInverses(0,type='a0')
        
        f1.pullback(f2,G3,A)
        """
        import itertools
        #First convert self and f2 into immersions
        self.stallings_folding()
        f2.stallings_folding()
        
        # G3 = GraphWithInverses()
        #A = AlphabetWithInverses(0,type='a0')
        d={}
        #get set of vertices 
        V = []
        for i in itertools.product(self.domain().vertices(),f2.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 f2.domain().alphabet().positive_letters():
                            if f2.domain().initial_vertex(e2) == v[1] and f2.domain().terminal_vertex(e2) == w[1]:
                                if self.image(e1)==f2.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 f2.codomain()
        
        return GraphMap(G3,G,n3)    
        
    @staticmethod
    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``.
        """

        graph=GraphWithInverses.rose_graph(automorphism.domain().alphabet().copy())
        return GraphMap(graph,graph,automorphism)