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])
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])
def Hilion_parabolic(k=1): """ Automorphism given in Section 2 of [Hilion]. This automorphism has a parabolic orbit inside F_4. REFERENCES: [Hilion] A. Hilion """ F=FreeGroup(4) phi=FreeGroupAutomorphism("a->a,b->ba,c->caa,d->dc",F) if k>1: phi=phi*pow(F.dehn_twist(c,a),k-1) return phi
def Hilion_parabolic(k=1): """ Automorphism given in Section 2 of [Hilion]. This automorphism has a parabolic orbit inside F_4. REFERENCES: [Hilion] A. Hilion, Dynamique des automorphismes des groupes libres, Thesis (Toulouse, 2004). """ F=FreeGroup(4) phi=FreeGroupAutomorphism("a->a,b->ba,c->caa,d->dc",F) if k>1: phi=phi*pow(F.dehn_twist(c,a),k-1) return phi
def Hilion_parabolic(k=1): """ Automorphism given in Section 2 of [Hilion]. This automorphism has a parabolic orbit inside F_4. REFERENCES: [Hilion] A. Hilion, Dynamique des automorphismes des groupes libres, Thesis (Toulouse, 2004). """ F = FreeGroup(4) phi = FreeGroupAutomorphism("a->a,b->ba,c->caa,d->dc", F) if k > 1: phi = phi * pow(F.dehn_twist(c, a), k - 1) return phi
def Bestvina_Handel_train_track_1_9(): """ Automorphism given as Example 1.9 in [BH-train-track] This automorphism cannot be represented by an absolute train-track. But the representation on the rose is a relative train-track. REFERENCES: [BH-train-track] M. Bestvina, M. Handel, Train tracks and automorphisms of free groups, Annals of Math, 135, 1-51, 1992. """ return FreeGroupAutomorphism("a->ba,b->bba,c->cAbaB", FreeGroup(3))
def Bestvina_Handel_train_track_3_6(): """ Automorphism given as Example 3.6 in [BH-train-track]. This automorphism is train-track on the rose and has an indivisble Nielsen path in A.b which is essential. REFERENCES: [BH-train-track] M. Bestvina, M. Handel, Train tracks and automorphisms of free groups, Annals of Math, 135, 1-51, 1992. """ return FreeGroupAutomorphism("a->ba,b->bba", FreeGroup(2))
def Levitt_Lustig_periodic(): """ Automorphism of F_3 given in Section 2 of [LL-periodic]. This is an atoroidal iwip. It is positive and thus train-track on the rose. REFERENCES: [LL-periodic] G. Levitt, and M. Lustig, Automorphisms of free groups have asymptotically periodic dynamics, """ return FreeGroupAutomorphism("a->cb,b->a,c->ba", FreeGroup(3))
def Clay_Pettet_twisting_out(): """ Automorphism of F_3 given in Section 2 of [CP-twisting]. This is an atoroidal iwip. It is positive and thus train-track on the rose. REFERENCES: [CP-twisting] M. Clay, and A. Pettet, Twisting out fully irreducible automorphisms, ArXiv:0906.4050 """ return FreeGroupAutomorphism("a->b,b->c,c->ab", FreeGroup(3))
def Bestvina_Handel_train_track_1_1(): """ Automorphism given as Example 1.1 in [BH-train-track]. This automorphism is iwip and not geometric nor parageometric. Its representative on the rose is train-track. Its inverse is also train-track on the rose. REFERENCES: [BH-train-track] M. Bestvina, M. Handel, Train tracks and automorphisms of free groups, Annals of Math, 135, 1-51, 1992. """ return FreeGroupAutomorphism("a->b,b->c,c->d,d->ADBC", FreeGroup(4))
def Bestvina_Handel_train_track_5_16(): """ Automorphism given as Example 5.16 in [BH-train-track]. This automorphism occurs as a pseudo-Anosov homeomorphism of the four-times punctured phere. Thus is it reducible. REFERENCES: [BH-train-track] M. Bestvina, M. Handel, Train tracks and automorphisms of free groups, Annals of Math, 135, 1-51, 1992. """ return FreeGroupAutomorphism("a->a,b->CAbac,c->CAbacacACABac", FreeGroup(3))
def Cohen_Lustig_7_3(): """ Automorphism given as example 7.3 in [CL-dynamics]. This is an atoroidal iwip. REFERENCES: [CL-dynamics] M. Cohen, M. Lustig, on the dynamics and the fixed subgroup of a free group automorphism, Inventiones Math. 96, 613-638, 1989. """ return FreeGroupAutomorphism("a->cabaa,b->baa,c->caba", FreeGroup(3))
def Cohen_Lustig_1_6(): """ Automorphism given as example 1.6 in [CL-dynamics]. It is reducible. REFERENCES: [CL-dynamics] M. Cohen, M. Lustig, on the dynamics and the fixed subgroup of a free group automorphism, Inventiones Math. 96, 613-638, 1989. """ return FreeGroupAutomorphism( "a->cccaCCC,b->CaccAbC,c->accAbccaCCBaCCAccccACCC", FreeGroup(3))
def Handel_Mosher_axes_5_5(): """ Automorphism given in Section 5.5 of [HM-axes] This automorphism phi is iwip and not geometric. Both phi and phi.inverse() are train-track on the rose. phi has expansion factor 6.0329 while phi.inverse() has expansion factor 4.49086. REFERENCES: [HM-axes] M. Handel, L. Mosher, axes in Outer space, Mem. Amer. Math. Soc. 213, 2011. """ return FreeGroupAutomorphism("a->bacaaca,b->baca,c->caaca", FreeGroup(3))
def Handel_Mosher_parageometric_1(): """ Automorphism given in the introduction of [HM-parageometric]. This automorphism phi is iwip, not geometric and parageometric. Both phi and phi.inverse() are train-track on the rose. phi has expansion factor 1.46557 while phi.inverse() has expansion factor 1.3247. REFERENCES: [HM-parageometric] M. Handel, L. Mosher, parageometric outer automorphisms of free groups, Transactions of Amer. Math. Soc. 359, 3153-3183, 2007. """ return FreeGroupAutomorphism("a->ac,b->a,c->b", FreeGroup(3))
def Handel_Mosher_axes_3_4(): """ Automorphism given in Section 3.4 of [HM-axes] This automorphism is iwip, not geometric and is train-track on the rose. It has expansion factor 4.0795. Its inverse is not train-track on the rose and has expansion factor 2.46557. It also appears in Section 5.5 of the paper. REFERENCES: [HM-axes] M. Handel, L. Mosher, axes in Outer space, Mem. Amer. Math. Soc. 213, 2011. """ A = AlphabetWithInverses(['a', 'g', 'f'], ['A', 'G', 'F']) return FreeGroupAutomorphism("a->afgfgf,f->fgf,g->gfafg", FreeGroup(A))
def Handel_Mosher_inverse_with_same_lambda(): """ Example given in the introduction of [HM-parageometric]. This is an iwip automorphisms that has the same expansion factor as its inverse: 3.199. It is not geometric and not parageometric. REFERECENCES: [HM-parageometric] M. Handel, L. Mosher, parageometric outer automorphisms of free groups, Transactions of Amer. Math. Soc. 359, 3153-3183, 2007. """ F = FreeGroup(3) theta = pow(FreeGroupAutomorphism("a->b,b->c,c->Ba", F), 4) psi = FreeGroupAutomorphism("a->b,b->a,c->c", F) return psi * theta * psi * theta.inverse()
def Turner_Stallings(): """ Automorphism of F_4 given in [Turner]. This automorphism comes from an idea of Stallings and although it is very short, it has a very long fixed word. It is a reducible automorphism. REFERENCES: [Turner] E. C. Turner, Finding indivisible Nielsen paths for a train tracks map, Proc. of a work- shop held at Heriot-Watt Univ., Edinburg, 1993 (Lond. Math. Soc. Lect. Note Ser., 204), Cambridge, Cambridge Univ. Press., 1995, 300-313. """ return FreeGroupAutomorphism("a->dac,b->CADac,c->CABac,d->CAbc", FreeGroup(4))
def Bestvina_Handel_surface_homeo(): """ Automorphism of F_4 given in [BH] see also [Kapovich]. This is a pseudo-Anosov mapping class of the 5-punctured sphere. Thus this is not an iwip. However, its representative on the rose in train-track. REFERENCES: [BH] M. Bestvina, and M. Handel, Train-tracks for surface homeomorphisms. Topology 34 (1995), no. 1, 109-140 [Kapovich] Ilya Kapovich, Algorithmic detectability of iwip automorphisms, arXiv:1209.3732 """ return FreeGroupAutomorphism("a->b,b->c,c->dA,d->DC", FreeGroup(4))
def _build_endmap(self, consolidate=False): """ Builds the map on ends for graph_map. Called when Core is initialized. """ alph = self._graph_map.domain()._alphabet inv_alph = self._inv_graph_map.domain()._alphabet FA = FreeGroup(alph) FB = FreeGroup(inv_alph) for x in self._graph_map.domain().edge_labels(): X = alph.inverse_letter(x) self._signed_ends[x] = [] self._signed_ends[X] = [] for x in self._inv_graph_map.domain().edge_labels(): X = inv_alph.inverse_letter(x) inv_image_x = self._inv_graph_map(x) vp = '' # vanishing path for a in inv_image_x: A = alph.inverse_letter(a) if vp == '' or vp[ -1] != X: # coming from + side, no cancellation self._signed_ends[a].append(vp + x) else: # coming from - side, cancels x self._signed_ends[a].append('-' + vp) # - + vp # # add to vanishing path vp = str(FB.reduced_product(str(self._graph_map(A)), vp)) # repeat for inverse, we've added to the vanishing path because we are # coming from the other side if vp == '' or vp[ -1] != X: # coming from - side, no cancellation self._signed_ends[A].append('-' + vp + x) # - + vp + x else: # coming from + side, cancels x self._signed_ends[A].append(vp) if consolidate: for a in self._signed_ends.keys(): removed_end = True N = 2 * inv_alph.cardinality( ) # should take into account the actual vertex... while removed_end: removed_end = False prefix = [] for e in self._signed_ends[a]: prefix.append(e[:-1]) prefix_counts = dict( (p, prefix.count(p)) for p in set(prefix)) for p in prefix_counts.keys(): p_opp = p[1:] if p[:1] == '-' else '-' + p removed_ends = [] tails = [] if prefix_counts[p] == N - 1: removed_end = True for e in self._signed_ends[a]: if e[:-1] == p: removed_ends.append(e) tails.append(e[-1:]) if p == '' or p == '-': for aa in inv_alph: if aa not in tails: self._signed_ends[a].append(p_opp + aa) else: self._signed_ends[a].append(p) for e in removed_ends: self._signed_ends[a].remove(e) if prefix_counts[ p] == N - 2 and p_opp in self._signed_ends[a]: removed_end = True for e in self._signed_ends[a]: if e[:-1] == p: removed_ends.append(e) tails.append(e[-1:]) tails.append(inv_alph.inverse_letter(p[-1:])) for aa in inv_alph: if aa not in tails: self._signed_ends[a].append(p_opp + aa) for e in removed_ends: self._signed_ends[a].remove(e) self._signed_ends[a].remove(p_opp)
def _build_endmap(self,consolidate=False): """ Builds the map on ends for graph_map. Called when Core is initialized. """ alph=self._graph_map.domain()._alphabet inv_alph=self._inv_graph_map.domain()._alphabet FA=FreeGroup(alph) FB=FreeGroup(inv_alph) for x in self._graph_map.domain().edge_labels(): X=alph.inverse_letter(x) self._signed_ends[x]=[] self._signed_ends[X]=[] for x in self._inv_graph_map.domain().edge_labels(): X=inv_alph.inverse_letter(x) inv_image_x = self._inv_graph_map(x) vp='' # vanishing path for a in inv_image_x: A=alph.inverse_letter(a) if vp=='' or vp[-1]!=X: # coming from + side, no cancellation self._signed_ends[a].append(vp + x) else: # coming from - side, cancels x self._signed_ends[a].append('-' + vp) # - + vp # # add to vanishing path vp=str(FB.reduced_product(str(self._graph_map(A)),vp)) # repeat for inverse, we've added to the vanishing path because we are # coming from the other side if vp=='' or vp[-1]!=X: # coming from - side, no cancellation self._signed_ends[A].append('-' + vp + x) # - + vp + x else: # coming from + side, cancels x self._signed_ends[A].append(vp) if consolidate: for a in self._signed_ends.keys(): removed_end=True N=2*inv_alph.cardinality() # should take into account the actual vertex... while removed_end: removed_end=False prefix=[] for e in self._signed_ends[a]: prefix.append(e[:-1]) prefix_counts=dict((p,prefix.count(p)) for p in set(prefix)) for p in prefix_counts.keys(): p_opp = p[1:] if p[:1]=='-' else '-'+p removed_ends=[] tails=[] if prefix_counts[p]==N-1: removed_end=True for e in self._signed_ends[a]: if e[:-1]==p: removed_ends.append(e) tails.append(e[-1:]) if p=='' or p=='-': for aa in inv_alph: if aa not in tails: self._signed_ends[a].append(p_opp+aa) else: self._signed_ends[a].append(p) for e in removed_ends: self._signed_ends[a].remove(e) if prefix_counts[p]==N-2 and p_opp in self._signed_ends[a]: removed_end=True for e in self._signed_ends[a]: if e[:-1]==p: removed_ends.append(e) tails.append(e[-1:]) tails.append(inv_alph.inverse_letter(p[-1:])) for aa in inv_alph: if aa not in tails: self._signed_ends[a].append(p_opp+aa) for e in removed_ends: self._signed_ends[a].remove(e) self._signed_ends[a].remove(p_opp)
def tribonacci(): """ Tribonacci automorphism. """ return FreeGroupAutomorphism("a->ab,b->ac,c->a", FreeGroup(3))
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)