Source code for surface_dynamics.interval_exchanges.labelled

r"""
Labelled permutations

A labelled (generalized) permutation is better suited to study the dynamic of
a translation surface than a reduced one (see the module
:mod:`surface_dynamics.interval_exchanges.reduced`). The latter is more adapted to the
study of strata. This kind of permutation was introduced by Yoccoz [Yoc06]_
(see also [MarMouYoc05]_).

In fact, there is a geometric counterpart of labelled permutations. They
correspond to translation surface with marked outgoing separatrices (i.e. we fi
a label for each of them).

Remarks that Rauzy diagram of reduced objects are significantly smaller than
the one for labelled object (for the permutation a b d b e / e d c a c the
labelled Rauzy diagram contains 8760 permutations, and the reduced only 73).
But, as it is in geometrical way, the labelled Rauzy diagram is a covering of
the reduced Rauzy diagram.

AUTHORS:

- Vincent Delecroix (2009-09-29) : initial version

- Vincent Delecroix (2010-02-11) : correction and simplification of datatypes

TESTS::

    sage: from surface_dynamics.interval_exchanges.labelled import LabelledPermutationIET
    sage: LabelledPermutationIET([['a','b','c'],['c','b','a']])
    a b c
    c b a
    sage: LabelledPermutationIET([[1,2,3,4],[4,1,2,3]])
    1 2 3 4
    4 1 2 3
    sage: from surface_dynamics.interval_exchanges.labelled import LabelledPermutationLI
    sage: LabelledPermutationLI([[1,1],[2,2,3,3,4,4]])
    1 1
    2 2 3 3 4 4
    sage: LabelledPermutationLI([['a','a','b','b','c','c'],['d','d']])
    a a b b c c
    d d
    sage: from surface_dynamics.interval_exchanges.labelled import FlippedLabelledPermutationIET
    sage: FlippedLabelledPermutationIET([[1,2,3],[3,2,1]],flips=[1,2])
    -1 -2  3
     3 -2 -1
    sage: FlippedLabelledPermutationIET([['a','b','c'],['b','c','a']],flips='b')
     a -b  c
    -b  c  a
    sage: from surface_dynamics.interval_exchanges.labelled import FlippedLabelledPermutationLI
    sage: FlippedLabelledPermutationLI([[1,1],[2,2,3,3,4,4]], flips=[1,4])
    -1 -1
     2  2  3  3 -4 -4
    sage: FlippedLabelledPermutationLI([['a','a','b','b'],['c','c']],flips='ac')
    -a -a  b  b
    -c -c
    sage: from surface_dynamics.interval_exchanges.labelled import LabelledRauzyDiagram
    sage: p = LabelledPermutationIET([[1,2,3],[3,2,1]])
    sage: d1 = LabelledRauzyDiagram(p)
    sage: p = LabelledPermutationIET([['a','b'],['b','a']])
    sage: d = p.rauzy_diagram()
    sage: g1 = d.path(p, 'top', 'bottom')
    sage: g1.matrix()
    [1 1]
    [1 2]
    sage: g2 = d.path(p, 'bottom', 'top')
    sage: g2.matrix()
    [2 1]
    [1 1]
    sage: p = LabelledPermutationIET([['a','b','c','d'],['d','c','b','a']])
    sage: d = p.rauzy_diagram()
    sage: g = d.path(p, 't', 't', 'b', 't', 'b', 'b', 't', 'b')
    sage: g
    Path of length 8 in a Rauzy diagram
    sage: g.is_loop()
    True
    sage: g.is_full()
    True
    sage: s1 = g.orbit_substitution()
    sage: print(s1)
    a->adbd, b->adbdbd, c->adccd, d->adcd
    sage: s2 = g.interval_substitution()
    sage: print(s2)
    a->abcd, b->bab, c->cdc, d->dcbababcd
    sage: s1.incidence_matrix() == s2.incidence_matrix().transpose()
    True
"""
#*****************************************************************************
#       Copyright (C) 2008 Vincent Delecroix <20100.delecroix@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  https://www.gnu.org/licenses/
#*****************************************************************************

from __future__ import print_function, absolute_import
from six.moves import range, map, filter, zip

from sage.structure.sage_object import SageObject

from copy import copy

from surface_dynamics.misc.permutation import perm_check, perm_init
import surface_dynamics.interval_exchanges.lyapunov_exponents as lyapunov_exponents  # the cython bindings

from sage.combinat.words.alphabet import Alphabet
from sage.combinat.words.morphism import WordMorphism

from sage.matrix.constructor import identity_matrix
from sage.rings.integer import Integer

from .template import (OrientablePermutationIET, OrientablePermutationLI,
                       FlippedPermutationIET, FlippedPermutationLI,
                       RauzyDiagram, FlippedRauzyDiagram,
                       interval_conversion, side_conversion)


[docs] class LabelledPermutation(SageObject): r""" General template for labelled objects. .. WARNING:: Internal class! Do not use directly! """ def __getitem__(self, i): r""" TESTS:: sage: from surface_dynamics import * sage: p = iet.Permutation([0,1,2,3],[3,2,1,0]) sage: p[0][0] 0 sage: p[1][2] 1 sage: p = iet.Permutation('a b c','c b a') sage: p[0] ['a', 'b', 'c'] sage: p[1] ['c', 'b', 'a'] sage: p = iet.Permutation('a b', 'b a', flips='a') sage: p[0] ['a', 'b'] sage: p = iet.GeneralizedPermutation('c p p', 't t c', flips='ct') sage: p[1] ['t', 't', 'c'] """ return list(map(self._alphabet.unrank, self._labels[i]))
[docs] def list(self, flips=False): r""" Returns a list of two lists corresponding to the intervals. INPUT: - ``flips`` - boolean (default: False) - if ``True`` returns instead of letters use pair of letter and flip. OUTPUT: two lists of labels (or labels with flips) EXAMPLES:: sage: from surface_dynamics import * The list of an permutation from iet:: sage: p1 = iet.Permutation('1 2 3', '3 1 2') sage: p1.list() [['1', '2', '3'], ['3', '1', '2']] sage: p1.alphabet("abc") sage: p1.list() [['a', 'b', 'c'], ['c', 'a', 'b']] Recovering the permutation from this list (and the alphabet):: sage: q1 = iet.Permutation(p1.list(),alphabet=p1.alphabet()) sage: p1 == q1 True The list of a quadratic permutation:: sage: p2 = iet.GeneralizedPermutation('g o o', 'd d g') sage: p2.list() [['g', 'o', 'o'], ['d', 'd', 'g']] Recovering the permutation:: sage: q2 = iet.GeneralizedPermutation(p2.list(),alphabet=p2.alphabet()) sage: p2 == q2 True Some non-orientable examples:: sage: p = iet.GeneralizedPermutation('0 0 1 2 2 1', '3 3', flips='1') sage: p.list(flips=True) [[('0', 1), ('0', 1), ('1', -1), ('2', 1), ('2', 1), ('1', -1)], [('3', 1), ('3', 1)]] sage: p.list(flips=False) [['0', '0', '1', '2', '2', '1'], ['3', '3']] sage: iet.Permutation('a b c', 'c b a').list(flips=True) [[('a', 1), ('b', 1), ('c', 1)], [('c', 1), ('b', 1), ('a', 1)]] The list can be used to reconstruct the permutation:: sage: p = iet.Permutation('a b c','c b a',flips='ab') sage: p == iet.Permutation(p.list(), flips=p.flips()) True :: sage: p = iet.GeneralizedPermutation('a b b c','c d d a',flips='ad') sage: p == iet.GeneralizedPermutation(p.list(), flips=p.flips()) True """ if flips: if self._flips is None: flips = [[1] * len(self._labels[0]), [1] * len(self._labels[1])] else: flips = self._flips a0 = list(zip(map(self._alphabet.unrank, self._labels[0]), flips[0])) a1 = list(zip(map(self._alphabet.unrank, self._labels[1]), flips[1])) else: a0 = list(map(self._alphabet.unrank, self._labels[0])) a1 = list(map(self._alphabet.unrank, self._labels[1])) return [a0,a1]
[docs] def relabel(self, p): r""" Relabel this permutation according to ``p``. EXAMPLES:: sage: from surface_dynamics import iet sage: p = iet.Permutation('a b c \n c b a') sage: p.relabel([2, 1, 0]) sage: p c b a a b c """ if not perm_check(p): p = perm_init(p, n=self._alphabet.cardinality()) for i in range(2): for j in range(len(self._labels[i])): self._labels[i][j] = p[self._labels[i][j]]
[docs] def rauzy_move_matrix(self, winner=None, side='right'): r""" Returns the Rauzy move matrix. This matrix corresponds to the action of a Rauzy move on the vector of lengths. By convention (to get a positive matrix), the matrix is define as the inverse transformation on the length vector. OUTPUT: matrix -- a square matrix of positive integers EXAMPLES: sage: from surface_dynamics import * :: sage: p = iet.Permutation('a b','b a') sage: p.rauzy_move_matrix('t') [1 0] [1 1] sage: p.rauzy_move_matrix('b') [1 1] [0 1] :: sage: p = iet.Permutation('a b c d','b d a c') sage: q = p.left_right_inverse() sage: m0 = p.rauzy_move_matrix(winner='top',side='right') sage: n0 = q.rauzy_move_matrix(winner='top',side='left') sage: m0 == n0 True sage: m1 = p.rauzy_move_matrix(winner='bottom',side='right') sage: n1 = q.rauzy_move_matrix(winner='bottom',side='left') sage: m1 == n1 True """ if winner is None and side is None: return identity_matrix(len(self)) winner = interval_conversion(winner) side = side_conversion(side) winner_letter = self._labels[winner][side] loser_letter = self._labels[1-winner][side] m = copy(identity_matrix(len(self))) m[winner_letter, loser_letter] = 1 return m
[docs] def rauzy_move_winner(self,winner=None,side=None): r""" Returns the winner of a Rauzy move. INPUT: - ``winner`` - either 'top' or 'bottom' ('t' or 'b' for short) - ``side`` - either 'left' or 'right' ('l' or 'r' for short) OUTPUT: -- a label EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c d','b d a c') sage: p.rauzy_move_winner('top','right') 'd' sage: p.rauzy_move_winner('bottom','right') 'c' sage: p.rauzy_move_winner('top','left') 'a' sage: p.rauzy_move_winner('bottom','left') 'b' :: sage: p = iet.GeneralizedPermutation('a b b c','d c a e d e') sage: p.rauzy_move_winner('top','right') 'c' sage: p.rauzy_move_winner('bottom','right') 'e' sage: p.rauzy_move_winner('top','left') 'a' sage: p.rauzy_move_winner('bottom','left') 'd' """ if winner is None and side is None: return None winner = interval_conversion(winner) side = side_conversion(side) return self[winner][side]
[docs] def rauzy_move_loser(self,winner=None,side=None): r""" Returns the loser of a Rauzy move INPUT: - ``winner`` - either 'top' or 'bottom' ('t' or 'b' for short) - ``side`` - either 'left' or 'right' ('l' or 'r' for short) OUTPUT: -- a label EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c d','b d a c') sage: p.rauzy_move_loser('top','right') 'c' sage: p.rauzy_move_loser('bottom','right') 'd' sage: p.rauzy_move_loser('top','left') 'b' sage: p.rauzy_move_loser('bottom','left') 'a' """ if winner is None and side is None: return None winner = interval_conversion(winner) side = side_conversion(side) return self[1-winner][side]
[docs] def LabelledPermutationsIET_iterator( nintervals=None, irreducible=True, alphabet=None): r""" Returns an iterator over labelled permutations. INPUT: - ``nintervals`` - integer or None - ``irreducible`` - boolean (default: True) - ``alphabet`` - something that should be converted to an alphabet of at least nintervals letters OUTPUT: iterator -- an iterator over permutations TESTS:: sage: from surface_dynamics import iet sage: for p in sorted(iet.Permutations_iterator(2, alphabet="ab")): ....: print("%s\n****" % p) #indirect doctest a b b a **** b a a b **** sage: for p in iet.Permutations_iterator(3, alphabet="abc"): ....: print("%s\n*****" %p) #indirect doctest a b c b c a ***** a b c c a b ***** a b c c b a ***** a c b b a c ***** a c b b c a ***** a c b c b a ***** b a c a c b ***** b a c c a b ***** b a c c b a ***** b c a a b c ***** b c a a c b ***** b c a c a b ***** c a b a b c ***** c a b b a c ***** c a b b c a ***** c b a a b c ***** c b a a c b ***** c b a b a c ***** """ from itertools import product from sage.combinat.permutation import Permutations if irreducible is False: if nintervals is None: raise ValueError("choose a number of intervals") else: assert(isinstance(nintervals,(int,Integer))) assert(nintervals > 0) def f(x): return LabelledPermutationIET([list(x[0]), list(x[1])], alphabet=alphabet, reduced=False) alphabet = Alphabet(alphabet) g = lambda x: [alphabet.unrank(k-1) for k in x] P = map(g, Permutations(nintervals)) return map(f,product(P,repeat=2)) else: return filter( lambda x: x.is_irreducible(), LabelledPermutationsIET_iterator(nintervals,False,alphabet))
[docs] class LabelledPermutationIET(LabelledPermutation, OrientablePermutationIET): """ Labelled permutation for iet EXAMPLES:: sage: from surface_dynamics import * Reducibility testing:: sage: p = iet.Permutation('a b c', 'c b a') sage: p.is_irreducible() True sage: q = iet.Permutation('a b c d', 'b a d c') sage: q.is_irreducible() False Rauzy movability and Rauzy move:: sage: p = iet.Permutation('a b c', 'c b a') sage: p.has_rauzy_move('top') True sage: p.rauzy_move('bottom') a c b c b a sage: p.has_rauzy_move('top') True sage: p.rauzy_move('top') a b c c a b Rauzy diagram:: sage: p = iet.Permutation('a b c', 'c b a') sage: d = p.rauzy_diagram() sage: p in d True """
[docs] def reduced(self): r""" Returns the associated reduced abelian permutation. OUTPUT: a reduced permutation -- the underlying reduced permutation EXAMPLES: sage: from surface_dynamics import * sage: p = iet.Permutation("a b c d","d c a b") sage: q = iet.Permutation("a b c d","d c a b",reduced=True) sage: p.reduced() == q True """ from .reduced import ReducedPermutationIET return ReducedPermutationIET(self.list(), alphabet=self._alphabet, reduced=True)
[docs] def rauzy_move_interval_substitution(self,winner=None,side=None): r""" Returns the interval substitution associated. INPUT: - ``winner`` - the winner interval ('top' or 'bottom') - ``side`` - (default: 'right') the side ('left' or 'right') OUTPUT: WordMorphism -- a substitution on the alphabet of the permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: print(p.rauzy_move_interval_substitution('top','right')) a->a, b->ba sage: print(p.rauzy_move_interval_substitution('bottom','right')) a->ab, b->b sage: print(p.rauzy_move_interval_substitution('top','left')) a->ba, b->b sage: print(p.rauzy_move_interval_substitution('bottom','left')) a->a, b->ab """ d = dict([(letter,[letter]) for letter in self.letters()]) if winner is None and side is None: return WordMorphism(d) winner = interval_conversion(winner) side = side_conversion(side) winner_letter = self.rauzy_move_winner(winner,side) loser_letter = self.rauzy_move_loser(winner,side) if side == 0: d[winner_letter] = [loser_letter,winner_letter] else: d[winner_letter] = [winner_letter,loser_letter] return WordMorphism(d)
[docs] def rauzy_move_orbit_substitution(self,winner=None,side=None): r""" Return the action of the rauzy_move on the orbit. INPUT: - ``i`` - integer - ``winner`` - the winner interval ('top' or 'bottom') - ``side`` - (default: 'right') the side ('right' or 'left') OUTPUT: WordMorphism -- a substitution on the alphabet of self EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: print(p.rauzy_move_orbit_substitution('top','right')) a->ab, b->b sage: print(p.rauzy_move_orbit_substitution('bottom','right')) a->a, b->ab sage: print(p.rauzy_move_orbit_substitution('top','left')) a->a, b->ba sage: print(p.rauzy_move_orbit_substitution('bottom','left')) a->ba, b->b TESTS:: sage: p = iet.Permutation('a1 a2', 'a2 a1') sage: p.rauzy_move_orbit_substitution('top','right').codomain().alphabet() {'a1', 'a2'} """ d = dict([(letter,[letter]) for letter in self.letters()]) if winner is None and side is None: return WordMorphism(d) winner = interval_conversion(winner) side = side_conversion(side) loser_letter = self.rauzy_move_loser(winner,side) top_letter = self.alphabet().unrank(self._labels[0][side]) bottom_letter = self.alphabet().unrank(self._labels[1][side]) d[loser_letter] = [bottom_letter,top_letter] return WordMorphism(d)
[docs] def rauzy_diagram(self, **args): """ Returns the associated Rauzy diagram. For more information try help(iet.RauzyDiagram). OUTPUT: Rauzy diagram -- the Rauzy diagram of the permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c', 'c b a') sage: d = p.rauzy_diagram() """ return LabelledRauzyDiagram(self, **args)
[docs] def lyapunov_exponents_approx(self, nb_vectors=None, nb_experiments=10, nb_iterations=65536, return_speed=False, verbose=False, output_file=None): r""" Return approximate Lyapunov exponents of the KZ-cocycle. EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation([1,2,3],[3,2,1]) sage: p.lyapunov_exponents_approx() # abs tol .1 [1.000] """ if self._flips: raise NotImplementedError("Lyapunov exponents not implemented for permutations with flips") c = self.cover([[0]]*len(self), as_tuple=True) return c.lyapunov_exponents_H_plus( nb_vectors=nb_vectors, nb_experiments=nb_experiments, nb_iterations=nb_iterations, return_speed=return_speed, verbose=verbose, output_file=output_file)
[docs] class LabelledPermutationLI(LabelledPermutation, OrientablePermutationLI): r""" Labelled quadratic (or generalized) permutation EXAMPLES:: sage: from surface_dynamics import * Reducibility testing:: sage: p = iet.GeneralizedPermutation('a b b', 'c c a') sage: p.is_irreducible() True Reducibility testing with associated decomposition:: sage: p = iet.GeneralizedPermutation('a b c a', 'b d d c') sage: p.is_irreducible() False sage: test, decomposition = p.is_irreducible(return_decomposition = True) sage: test False sage: decomposition (['a'], ['c', 'a'], [], ['c']) Rauzy movability and Rauzy move:: sage: p = iet.GeneralizedPermutation('a a b b c c', 'd d') sage: p.has_rauzy_move(0) False sage: p.has_rauzy_move(1) True sage: q = p.rauzy_move(1) sage: q a a b b c c d d sage: q.has_rauzy_move(0) True sage: q.has_rauzy_move(1) True Rauzy diagrams:: sage: p = iet.GeneralizedPermutation('0 0 1 1','2 2') sage: r = p.rauzy_diagram() sage: p in r True """
[docs] def has_right_rauzy_move(self, winner): r""" Test of Rauzy movability with a specified winner) A quadratic (or generalized) permutation is rauzy_movable type depending on the possible length of the last interval. It's dependent of the length equation. INPUT: - ``winner`` - 'top' (or 't' or 0) or 'bottom' (or 'b' or 1) OUTPUT: bool -- True if self has a Rauzy move EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a','b b') sage: p.has_right_rauzy_move('top') False sage: p.has_right_rauzy_move('bottom') False :: sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: p.has_right_rauzy_move('top') True sage: p.has_right_rauzy_move('bottom') True :: sage: p = iet.GeneralizedPermutation('a a','b b c c') sage: p.has_right_rauzy_move('top') True sage: p.has_right_rauzy_move('bottom') False :: sage: p = iet.GeneralizedPermutation('a a b b','c c') sage: p.has_right_rauzy_move('top') False sage: p.has_right_rauzy_move('bottom') True """ winner = interval_conversion(winner) loser = self._labels[1 - winner][-1] # the same letter at the right-end (False) if self._labels[0][-1] == self._labels[1][-1]: return False # the winner (or loser) letter is repeated on the other interval (True) if self._labels[0][-1] in self._labels[1]: return True if self._labels[1][-1] in self._labels[0]: return True # the loser letters is the only letter repeated in the loser # interval (False) for i,c in enumerate((self._labels[1-winner])): if c != loser and c in self._labels[1-winner][i+1:]: return True return False
[docs] def right_rauzy_move(self, winner): r""" Perform a Rauzy move on the right (the standard one). INPUT: - ``winner`` - 'top' (or 't' or 0) or 'bottom' (or 'b' or 1) OUTPUT: boolean -- True if self has a Rauzy move EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: p.right_rauzy_move(0) a a b b c c sage: p.right_rauzy_move(1) a a b b c c :: sage: p = iet.GeneralizedPermutation('a b b','c c a') sage: p.right_rauzy_move(0) a a b b c c sage: p.right_rauzy_move(1) a b b c c a TESTS:: sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: q = p.top_bottom_inverse() sage: q = q.right_rauzy_move(0) sage: q = q.top_bottom_inverse() sage: q == p.right_rauzy_move(1) True sage: q = p.top_bottom_inverse() sage: q = q.right_rauzy_move(1) sage: q = q.top_bottom_inverse() sage: q == p.right_rauzy_move(0) True sage: p = p.left_right_inverse() sage: q = q.left_rauzy_move(0) sage: q = q.left_right_inverse() sage: q == p.right_rauzy_move(0) True sage: q = p.left_right_inverse() sage: q = q.left_rauzy_move(1) sage: q = q.left_right_inverse() sage: q == p.right_rauzy_move(1) True """ result = copy(self) winner_letter = result._labels[winner][-1] loser_letter = result._labels[1-winner].pop(-1) if winner_letter in result._labels[winner][:-1]: loser_to = result._labels[winner].index(winner_letter) result._labels[winner].insert(loser_to, loser_letter) else: loser_to = result._labels[1-winner].index(winner_letter) + 1 result._labels[1-winner].insert(loser_to, loser_letter) return result
[docs] def left_rauzy_move(self, winner): r""" Perform a Rauzy move on the left. INPUT: - ``winner`` - 'top' or 'bottom' OUTPUT: permutation -- the Rauzy move of self EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: p.left_rauzy_move(0) a a b b c c sage: p.left_rauzy_move(1) a a b b c c :: sage: p = iet.GeneralizedPermutation('a b b','c c a') sage: p.left_rauzy_move(0) a b b c c a sage: p.left_rauzy_move(1) b b c c a a TESTS:: sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: q = p.top_bottom_inverse() sage: q = q.left_rauzy_move(0) sage: q = q.top_bottom_inverse() sage: q == p.left_rauzy_move(1) True sage: q = p.top_bottom_inverse() sage: q = q.left_rauzy_move(1) sage: q = q.top_bottom_inverse() sage: q == p.left_rauzy_move(0) True sage: q = p.left_right_inverse() sage: q = q.right_rauzy_move(0) sage: q = q.left_right_inverse() sage: q == p.left_rauzy_move(0) True sage: q = p.left_right_inverse() sage: q = q.right_rauzy_move(1) sage: q = q.left_right_inverse() sage: q == p.left_rauzy_move(1) True """ result = copy(self) winner_letter = result._labels[winner][0] loser_letter = result._labels[1-winner].pop(0) if winner_letter in result._labels[winner][1:]: loser_to = result._labels[winner][1:].index(winner_letter)+2 result._labels[winner].insert(loser_to, loser_letter) else: loser_to = result._labels[1-winner].index(winner_letter) result._labels[1-winner].insert(loser_to, loser_letter) return result
[docs] def reduced(self): r""" Returns the associated reduced quadratic permutations. OUTPUT: permutation -- the underlying reduced permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a','b b c c') sage: q = p.reduced() sage: q a a b b c c sage: p.rauzy_move(0).reduced() == q.rauzy_move(0) True """ from .reduced import ReducedPermutationLI return ReducedPermutationLI(self.list(),alphabet=self._alphabet, reduced=True)
[docs] def rauzy_diagram(self, **kargs): r""" Returns the associated RauzyDiagram. OUTPUT: Rauzy diagram -- the Rauzy diagram of the permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a b c b', 'c d d a') sage: d = p.rauzy_diagram() sage: p in d True For more information, try help(iet.RauzyDiagram) """ return LabelledRauzyDiagram(self, **kargs)
[docs] def lyapunov_exponents_H_plus(self, nb_vectors=None, nb_experiments=10, nb_iterations=65536, return_speed=False, verbose=False, output_file=None): r""" Compute the H^+ Lyapunov exponents of the stratum associated to this permutation. This method calls a C library. It might be significantly faster if ``nb_vectors=1`` (or if it is not provided but genus is 1). INPUT: - ``nb_vectors`` -- the number of exponents to compute. The number of vectors must not exceed the dimension of the space! - ``nb_experiments`` -- the number of experiments to perform. It might be around 100 (default value) in order that the estimation of confidence interval is accurate enough. - ``nb_iterations`` -- the number of iteration of the Rauzy-Zorich algorithm to perform for each experiments. The default is 2^15=32768 which is rather small but provide a good compromise between speed and quality of approximation. - ``verbose`` -- if ``True`` provide additional information rather than returning only the Lyapunov exponents (i.e. elapsed time, confidence intervals, ...) - ``output_file`` -- if provided (as a file object or a string) output the additional information in the given file rather than on the standard output. EXAMPLES:: sage: from surface_dynamics import * sage: Q = Stratum([1,1,-1,-1], k=2).unique_component() sage: p = Q.permutation_representative(reduced=False) sage: p.lyapunov_exponents_H_plus(nb_iterations=2**20) # abs tol .1 [0.6666] sage: Q_reg = Stratum([12], k=2).regular_component() sage: p_reg = Q_reg.permutation_representative(reduced=False) sage: lexp = p_reg.lyapunov_exponents_H_plus(nb_iterations=2**20) sage: lexp # random [0.662, 0.448, 0.230, 0.087] sage: len(lexp) 4 sage: sum(lexp) # abs tol .1 1.428 sage: Q_irr = Stratum([12], k=2).irregular_component() sage: p_irr = Q_irr.permutation_representative(reduced=False) sage: lexp = p_irr.lyapunov_exponents_H_plus(nb_iterations=2**20) sage: lexp # random [0.747, 0.491, 0.245, 0.090] sage: len(lexp) 4 sage: sum(lexp) # abs tol .1 1.572 """ if self._flips: raise NotImplementedError("Lyapunov exponents not implemented for permutations with flips") c = self.cover([[0]]*len(self), as_tuple=True) return c.lyapunov_exponents_H_plus( nb_vectors=nb_vectors, nb_experiments=nb_experiments, nb_iterations=nb_iterations, return_speed=return_speed, verbose=verbose, output_file=output_file)
[docs] def lyapunov_exponents_H_minus(self, nb_vectors=None, nb_experiments=10, nb_iterations=65536, return_speed=False, verbose=False, output_file=None): r""" Compute the H^+ Lyapunov exponents of the stratum associated to this permutation. This method calls a C library. It might be significantly faster if ``nb_vectors=1`` (or if it is not provided but genus is 1). INPUT: - ``nb_vectors`` -- the number of exponents to compute. The number of vectors must not exceed the dimension of the space! - ``nb_experiments`` -- the number of experiments to perform. It might be around 100 (default value) in order that the estimation of confidence interval is accurate enough. - ``nb_iterations`` -- the number of iteration of the Rauzy-Zorich algorithm to perform for each experiments. The default is `2^16=65536` which is rather small but provide a good compromise between speed and quality of approximation. - ``verbose`` -- if ``True`` provide additional information rather than returning only the Lyapunov exponents (i.e. elapsed time, confidence intervals, ...) - ``output_file`` -- if provided (as a file object or a string) output the additional information in the given file rather than on the standard output. EXAMPLES:: sage: from surface_dynamics import * sage: Q = Stratum([1,1,-1,-1], k=2).unique_component() sage: p = Q.permutation_representative(reduced=False) sage: lexp = p.lyapunov_exponents_H_minus(nb_iterations=2**20) sage: lexp # random [1.000, 0.333] sage: len(lexp) 2 sage: sum(lexp) # abs tol .1 1.333 sage: Q_reg = Stratum([12], k=2).regular_component() sage: p_reg = Q_reg.permutation_representative(reduced=False) sage: lexp = p_reg.lyapunov_exponents_H_minus(nb_iterations=2**19) sage: lexp # random [1.000, 0.309, 0.119] sage: len(lexp) 3 sage: sum(lexp) # abs tol .1 1.428 sage: Q_irr = Stratum([12], k=2).irregular_component() sage: p_irr = Q_irr.permutation_representative(reduced=False) sage: lexp = p_irr.lyapunov_exponents_H_minus(nb_iterations=2**19) sage: lexp # random [1.000, 0.444, 0.128] sage: len(lexp) 3 sage: sum(lexp) # abs tol .1 1.5725 """ if self._flips: raise NotImplementedError("Lyapunov exponents not implemented for permutations with flips") # we know that the double cover gives rise to two characters. We need to # find the one corresponding to H^-. We just pick the one which is not # constantly 1 and correspond to H^+. c = self.orientation_cover() c0,c1 = c._real_characters()[0] i0 = (-1 in c0) i1 = (-1 in c1) if i0 and i1: raise RuntimeError("not a generalized permutation") elif i0: character = c0 elif i1: character = c1 else: raise RuntimeError("trouble with permutation={}".format(self)) return c.lyapunov_exponents_H_plus( nb_vectors=nb_vectors, nb_experiments=nb_experiments, nb_iterations=nb_iterations, return_speed=return_speed, isotypic_decomposition=character, verbose=verbose, output_file=output_file)[0]
[docs] class FlippedLabelledPermutationIET(FlippedPermutationIET, LabelledPermutationIET): r""" Flipped labelled permutation from iet. EXAMPLES:: sage: from surface_dynamics import * Reducibility testing (does not depends of flips):: sage: p = iet.Permutation('a b c', 'c b a',flips='a') sage: p.is_irreducible() True sage: q = iet.Permutation('a b c d', 'b a d c', flips='bc') sage: q.is_irreducible() False Rauzy movability and Rauzy move:: sage: p = iet.Permutation('a b c', 'c b a',flips='a') sage: p -a b c c b -a sage: p.rauzy_move(1) -c -a b -c b -a sage: p.rauzy_move(0) -a b c c -a b Rauzy diagrams:: sage: d = iet.RauzyDiagram('a b c d','d a b c',flips='a') """
[docs] def reduced(self): r""" The associated reduced permutation. OUTPUT: permutation -- the associated reduced permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a',flips='a') sage: q = iet.Permutation('a b c','c b a',flips='a',reduced=True) sage: p.reduced() == q True """ from surface_dynamics.interval_exchanges.reduced import FlippedReducedPermutationIET return FlippedReducedPermutationIET( intervals=self.list(flips=False), flips=self.flips(), alphabet=self.alphabet(), reduced=True)
[docs] def rauzy_diagram(self, **kargs): r""" Returns the Rauzy diagram associated to this permutation. For more information, try help(iet.RauzyDiagram) OUTPUT: RauzyDiagram -- the Rauzy diagram of self EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c', 'c b a',flips='a') sage: p.rauzy_diagram() Rauzy diagram with 3 permutations """ return FlippedLabelledRauzyDiagram(self, **kargs)
[docs] class FlippedLabelledPermutationLI(FlippedPermutationLI, LabelledPermutationLI): r""" Flipped labelled quadratic (or generalized) permutation. EXAMPLES:: sage: from surface_dynamics import * Rauzy movability and Rauzy move:: sage: p = iet.GeneralizedPermutation('a a b b c c', 'd d', flips='d') sage: p.has_rauzy_move(0) False sage: p.has_rauzy_move(1) True sage: p = iet.GeneralizedPermutation('a a b','b c c',flips='c') sage: p.has_rauzy_move(0) True sage: p.has_rauzy_move(1) True """
[docs] def reduced(self): r""" The associated reduced permutation. OUTPUT: permutation -- the associated reduced permutation EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a','b b c c',flips='a') sage: q = iet.GeneralizedPermutation('a a','b b c c',flips='a',reduced=True) sage: p.reduced() == q True """ from surface_dynamics.interval_exchanges.reduced import FlippedReducedPermutationLI return FlippedReducedPermutationLI( intervals=self.list(flips=False), flips=self.flips(), alphabet=self.alphabet(), reduced=True)
[docs] def right_rauzy_move(self, winner): r""" Perform a Rauzy move on the right (the standard one). INPUT: - ``winner`` - either 'top' or 'bottom' ('t' or 'b' for short) OUTPUT: permutation -- the Rauzy move of self EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a a b','b c c',flips='c') sage: p.right_rauzy_move(0) a a b -c b -c sage: p.right_rauzy_move(1) a a -b -c -b -c :: sage: p = iet.GeneralizedPermutation('a b b','c c a',flips='ab') sage: p.right_rauzy_move(0) a -b a -b c c sage: p.right_rauzy_move(1) b -a b c c -a """ result = copy(self) winner_letter = result._labels[winner][-1] winner_flip = result._flips[winner][-1] loser_letter = result._labels[1-winner].pop(-1) loser_flip = result._flips[1-winner].pop(-1) if loser_letter in result._labels[winner]: loser_twin = result._labels[winner].index(loser_letter) result._flips[winner][loser_twin] = loser_flip*winner_flip else: loser_twin = result._labels[1-winner].index(loser_letter) result._flips[1-winner][loser_twin] = loser_flip*winner_flip if winner_letter in result._labels[winner][:-1]: loser_to = result._labels[winner].index(winner_letter) if winner_flip == -1: loser_to += 1 result._labels[winner].insert(loser_to, loser_letter) result._flips[winner].insert(loser_to, loser_flip*winner_flip) else: loser_to = result._labels[1-winner].index(winner_letter) if loser_flip == 1: loser_to += 1 result._labels[1-winner].insert(loser_to, loser_letter) result._flips[1-winner].insert(loser_to, loser_flip*winner_flip) return result
[docs] def left_rauzy_move(self, winner): r""" Perform a Rauzy move on the left. INPUT: - ``winner`` - either 'top' or 'bottom' ('t' or 'b' for short) OUTPUT: -- a permutation EXAMPLES:: sage: from surface_dynamics import * :: sage: p = iet.GeneralizedPermutation('a a b','b c c') sage: p.left_rauzy_move(0) a a b b c c sage: p.left_rauzy_move(1) a a b b c c :: sage: p = iet.GeneralizedPermutation('a b b','c c a') sage: p.left_rauzy_move(0) a b b c c a sage: p.left_rauzy_move(1) b b c c a a """ result = copy(self) winner_letter = result._labels[winner][0] loser_letter = result._labels[1-winner].pop(0) if winner_letter in result._labels[winner][1:]: loser_to = result._labels[winner][1:].index(winner_letter)+2 result._labels[winner].insert(loser_to, loser_letter) else: loser_to = result._labels[1-winner].index(winner_letter) result._labels[1-winner].insert(loser_to, loser_letter) return result
[docs] def rauzy_diagram(self, **kargs): r""" Returns the associated Rauzy diagram. For more information, try help(RauzyDiagram) OUTPUT : -- a RauzyDiagram EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.GeneralizedPermutation('a b b a', 'c d c d') sage: d = p.rauzy_diagram() """ return FlippedLabelledRauzyDiagram(self, **kargs)
[docs] class LabelledRauzyDiagram(RauzyDiagram): r""" Template for Rauzy diagrams of labelled permutations. ...DO NOT USE... """
[docs] class Path(RauzyDiagram.Path): r""" Path in Labelled Rauzy diagram. """
[docs] def matrix(self): r""" Returns the matrix associated to a path. The matrix associated to a Rauzy induction, is the linear application that allows to recover the lengths of self from the lengths of the induced. OUTPUT: matrix -- a square matrix of integers EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a1 a2','a2 a1') sage: d = p.rauzy_diagram() sage: g = d.path(p,'top') sage: g.matrix() [1 0] [1 1] sage: g = d.path(p,'bottom') sage: g.matrix() [1 1] [0 1] :: sage: p = iet.Permutation('a b c','c b a') sage: d = p.rauzy_diagram() sage: g = d.path(p) sage: g.matrix() == identity_matrix(3) True sage: g = d.path(p,'top') sage: g.matrix() [1 0 0] [0 1 0] [1 0 1] sage: g = d.path(p,'bottom') sage: g.matrix() [1 0 1] [0 1 0] [0 0 1] """ return self.composition(self._parent.edge_to_matrix)
[docs] def interval_substitution(self): r""" Returns the substitution of intervals obtained. OUTPUT: WordMorphism -- the word morphism corresponding to the interval EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: r = p.rauzy_diagram() sage: p0 = r.path(p,0) sage: s0 = p0.interval_substitution() sage: print(s0) a->a, b->ba sage: p1 = r.path(p,1) sage: s1 = p1.interval_substitution() sage: print(s1) a->ab, b->b sage: (p0 + p1).interval_substitution() == s1 * s0 True sage: (p1 + p0).interval_substitution() == s0 * s1 True """ return self.right_composition(self._parent.edge_to_interval_substitution)
[docs] def orbit_substitution(self): r""" Returns the substitution on the orbit of the left extremity. OUTPUT: WordMorphism -- the word morphism corresponding to the orbit EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: d = p.rauzy_diagram() sage: g0 = d.path(p,'top') sage: s0 = g0.orbit_substitution() sage: print(s0) a->ab, b->b sage: g1 = d.path(p,'bottom') sage: s1 = g1.orbit_substitution() sage: print(s1) a->a, b->ab sage: (g0 + g1).orbit_substitution() == s0 * s1 True sage: (g1 + g0).orbit_substitution() == s1 * s0 True """ return self.composition(self._parent.edge_to_orbit_substitution)
substitution = orbit_substitution # standard name dual_substitution = interval_substitution # standard name
[docs] def is_full(self): r""" Tests the fullness. A path is full if all intervals win at least one time. OUTPUT: boolean -- True if the path is full and False else EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a') sage: r = p.rauzy_diagram() sage: g0 = r.path(p,'t','b','t') sage: g1 = r.path(p,'b','t','b') sage: g0.is_full() False sage: g1.is_full() False sage: (g0 + g1).is_full() True sage: (g1 + g0).is_full() True """ return set(self._parent.letters()) == set(self.winners())
[docs] def self_similar_iet(self, name='a'): r""" Return the self-similar interval exchange transformation associated to this path INPUT: - ``name`` - an optional name for the generator of the number field EXAMPLES:: sage: from surface_dynamics import * The golden rotation:: sage: p = iet.Permutation('a b', 'b a') sage: R = p.rauzy_diagram() sage: g = R.path(p, 't', 'b') sage: T = g.self_similar_iet() sage: T.lengths().parent() Vector space of dimension 2 over Number Field ... sage: T.lengths().n() (1.00000000000000, 1.61803398874989) An example from Do-Schmidt:: sage: code = [1,0,1,0,1,0,0,0,1,0,0,1,1,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,1,0] sage: p = iet.Permutation([0,1,2,3,4,5,6],[6,5,4,3,2,1,0]) sage: R = p.rauzy_diagram() sage: g = R.path(p, *code) sage: T = g.self_similar_iet() sage: T.sah_arnoux_fathi_invariant() (0, 0, 0) """ if not self.is_loop() or not self.is_full(): raise ValueError("the path must be a full loop") from sage.rings.qqbar import AA from sage.rings.number_field.number_field import NumberField m = self.matrix() poly = m.charpoly() l = max(poly.roots(AA, False)) K = NumberField(l.minpoly(), name=name, embedding=l) a = K.gen() lengths = (m - a).right_kernel().basis()[0] if any(x <= 0 for x in lengths): raise RuntimeError("wrong Perron-Frobenius eigenvector: {}".format(lengths)) # NOTE: the above code makes "lengths" with parent being the right kernel (that is # a submodule of R^d) lengths = lengths.parent().ambient_vector_space()(lengths) from .iet import IntervalExchangeTransformation return IntervalExchangeTransformation(self.start(), lengths)
[docs] def edge_to_interval_substitution(self, p=None, edge_type=None): r""" Returns the interval substitution associated to an edge OUTPUT: WordMorphism -- the WordMorphism corresponding to the edge EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a') sage: r = p.rauzy_diagram() sage: print(r.edge_to_interval_substitution(None,None)) a->a, b->b, c->c sage: print(r.edge_to_interval_substitution(p,0)) a->a, b->b, c->ca sage: print(r.edge_to_interval_substitution(p,1)) a->ac, b->b, c->c """ if p is None and edge_type is None: return WordMorphism(dict((a,[a]) for a in self.letters())) function_name = self._edge_types[edge_type][0] + '_interval_substitution' if not hasattr(self._element_class,function_name): return WordMorphism(dict((a,[a]) for a in self.letters())) arguments = self._edge_types[edge_type][1] return getattr(p,function_name)(*arguments)
[docs] def edge_to_orbit_substitution(self, p=None, edge_type=None): r""" Returns the interval substitution associated to an edge OUTPUT: WordMorphism -- the word morphism corresponding to the edge EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a') sage: r = p.rauzy_diagram() sage: print(r.edge_to_orbit_substitution(None,None)) a->a, b->b, c->c sage: print(r.edge_to_orbit_substitution(p,0)) a->ac, b->b, c->c sage: print(r.edge_to_orbit_substitution(p,1)) a->a, b->b, c->ac TESTS:: sage: from surface_dynamics import * sage: pi0 = iet.Permutation('A1 A2 B', 'B A1 A2') sage: G = pi0.rauzy_diagram() sage: s1 = G.edge_to_orbit_substitution(pi0,0) sage: s1.domain().alphabet() {'A1', 'A2', 'B'} sage: s1.codomain().alphabet() {'A1', 'A2', 'B'} """ if p is None and edge_type is None: return WordMorphism(dict((a,[a]) for a in self.letters())) function_name = self._edge_types[edge_type][0] + '_orbit_substitution' if not hasattr(self._element_class,function_name): return WordMorphism(dict((a,[a]) for a in self.letters())) arguments = self._edge_types[edge_type][1] return getattr(p,function_name)(*arguments)
[docs] def full_loop_iterator(self, start=None, max_length=1): r""" Returns an iterator over all full path starting at start. INPUT: - ``start`` - the start point - ``max_length`` - a limit on the length of the paths OUTPUT: iterator -- iterator over full loops EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: r = p.rauzy_diagram() sage: for g in r.full_loop_iterator(p,2): ....: print("%s\n*****" % g.matrix()) [1 1] [1 2] ***** [2 1] [1 1] ***** """ g = self.path(start) ifull = filter( lambda x: x.is_loop() and x.is_full(), self._all_path_extension(g,max_length)) return map(copy,ifull)
[docs] def full_nloop_iterator(self, start=None, length=1): r""" Returns an iterator over all full loops of given length. INPUT: - ``start`` - the initial permutation - ``length`` - the length to consider OUTPUT: iterator -- an iterator over the full loops of given length EXAMPLES:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a') sage: d = p.rauzy_diagram() sage: for g in d.full_nloop_iterator(p,2): ....: print("%s\n*****" % g.matrix()) [1 1] [1 2] ***** [2 1] [1 1] ***** """ g = self.path(start) ifull = filter( lambda x: x.is_loop() and x.is_full(), self._all_npath_extension(g,length)) return map(copy, ifull)
def _permutation_to_vertex(self, p): r""" Translation of a labelled permutation to a vertex INPUT: - ``p`` - a labelled Permutation TESTS:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a') sage: r = p.rauzy_diagram() sage: p in r #indirect doctest True """ return ( tuple(p._labels[0]),tuple(p._labels[1]), tuple(p._twin[0]),tuple(p._twin[1])) def _set_element(self,data): r""" Sets self._element with data TESTS:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a') sage: r = p.rauzy_diagram() sage: r[p][0] == p.rauzy_move(0) #indirect doctest True sage: r[p][1] == p.rauzy_move(1) #indirect doctest True """ self._element._labels = [list(data[0]), list(data[1])] self._element._twin = [list(data[2]), list(data[3])]
[docs] class FlippedLabelledRauzyDiagram(FlippedRauzyDiagram, LabelledRauzyDiagram): r""" Rauzy diagram of flipped labelled permutations """ def _permutation_to_vertex(self, p): r""" Returns what must be stored from p. INPUT: - ``p`` - a Flipped labelled permutation TESTS:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b c','c b a',flips='a') sage: r = p.rauzy_diagram() sage: p in r #indirect doctest True """ return (tuple(p._labels[0]),tuple(p._labels[1]), tuple(p._twin[0]), tuple(p._twin[1]), tuple(p._flips[0]), tuple(p._flips[1])) def _set_element(self, data): r""" Returns what the vertex i as a permutation. TESTS:: sage: from surface_dynamics import * sage: p = iet.Permutation('a b','b a',flips='a') sage: r = p.rauzy_diagram() sage: p in r #indirect doctest True """ self._element._labels = [list(data[0]), list(data[1])] self._element._twin = [list(data[2]), list(data[3])] self._element._flips = [list(data[4]), list(data[5])]