"""
Dense origamis

An *origami* is a pair `(r, u)` of permutations up to conjugacy.
The canonical representative of an origami is computed by an
independent C program in ``normal_form.c``.

A *pillowcase cover* is a quadruple `(g_0, g_1, g_2, g_3)` of permutations
such that the product `g_0 g_1 g_2 g_3` is the identity. It is sometimes
called a 4-constellation. The canonical representative of a pillowcase cover
is computed by an independent C program in ``normal_form.c``.
"""
# ****************************************************************************
#       Copyright (C) 2011-2019 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 sage.ext.interrupt.interrupt cimport sig_on, sig_off
#from sage.ext.memory cimport sage_malloc, sage_free

from __future__ import print_function, absolute_import, division
from six import iteritems, iterkeys

from cpython.list cimport *
from cpython.tuple cimport *

from cpython cimport bool

try:
    from sage.structure.coerce import CoercionModel
except ImportError:
    # before Sage 8.6, CoercionModel used to be somewhere else
    from sage.structure.element import CoercionModel

from sage.rings.integer cimport Integer, smallInteger
from sage.rings.integer import GCD_list

from libc.stdlib cimport calloc, malloc, free
from libc.string cimport memset, memcpy
from libc.limits cimport UINT_MAX

from sage.groups.perm_gps.permgroup import PermutationGroupElement
try:
    # Trac #28652: Rework the constructor of PermutationGroupElement
    # (in 9.1.beta)
    from sage.groups.perm_gps.constructor import PermutationGroupElement as PermutationConstructor
except ImportError:
    PermutationConstructor = PermutationGroupElement

from sage.misc.cachefunc import cached_method

from sage.libs.gap.libgap import libgap

from sage.misc.decorators import options

cdef extern from "normal_form.h":
    int origami_normal_form(int *x, int *y, int *ren, unsigned int n)
    int pillowcase_cover_normal_form(int *g, int *ren, unsigned int n)
    # TODO: inline troubles
    int origami_diff(int *o1, int *o2, unsigned int n)
    int pillowcase_cover_diff(int *g1, int *g2, unsigned int n)

cdef extern from "lyapunov_exponents.h":
    ctypedef struct origami_data
    origami_data * new_origami_data(size_t degree, size_t nb_vectors, int *pa, int *pb)
    void free_origami_data(origami_data *o)
    void lyapunov_exponents(origami_data *o, size_t nb_iterations, double *theta)

    ctypedef struct origami_with_involution_data
    origami_with_involution_data * new_origami_with_involution_data(size_t degree, size_t nb_vectors_p, size_t nb_vectors_m, int *pa, int *pb, int *s)
    void free_origami_with_involution_data(origami_with_involution_data *o)
    void lyapunov_exponents_with_involution(origami_with_involution_data *o, size_t NB_ITERATIONS, double * ttheta)

def origami_from_gap_permutations(r, u):
    r"""
    TESTS::

        sage: from surface_dynamics.flat_surfaces.origamis.origami_dense import origami_from_gap_permutations
        sage: r = gap("(1,2)(3,4)")  # not tested
        sage: u = gap("(2,3)")       # not tested
        sage: origami_from_gap_permutations(r, u)  # not tested
        (1,2)(3,4)
        (1)(2,3)(4)

        sage: r = libgap.eval("(1,2)(3,4)")
        sage: u = libgap.eval("(2,3)")
        sage: origami_from_gap_permutations(r, u)
        (1,2)(3,4)
        (1)(2,3)(4)
    """
    r = PermutationConstructor(r)
    u = PermutationConstructor(u)
    n = 0
    if r.domain():
        n = max(n, max(r.domain()))
    if u.domain():
        n = max(n, max(u.domain()))
    r = [r(i+1)-1 for i in range(n)]
    u = [u(i+1)-1 for i in range(n)]
    return Origami_dense_pyx(tuple(r), tuple(u))

def lattice(vectors):
    r"""
    Return a triple `(a, t, u)` where `((a, 0), (t, u))` is a basis for the
    integer lattice generated by ``vectors``.

    EXAMPLES::

        sage: from surface_dynamics.flat_surfaces.origamis.origami_dense import lattice

        sage: lattice([(2, 0), (3, 1)])
        (2, 1, 1)
        sage: lattice([(2, 0), (3, 1), (2, 1)])
        (1, 0, 1)
        sage: lattice([(1, 2), (3, 6), (5, 10)])
        Traceback (most recent call last):
        ...
        ValueError: all input vectors are collinear

    TESTS::

        sage: from surface_dynamics.flat_surfaces.origamis.origami_dense import lattice

        sage: m0 = matrix(ZZ, 2, 2, [50, 51, 49, 50])
        sage: m1 = matrix(ZZ, 2, 2, [-92, 29, -73, 23])
        sage: m2 = matrix(ZZ, 2, 2, [-61, 84, 53, -73])
        sage: m3 = matrix(ZZ, 2, 2, [-54, -71, -73, -96])
        sage: m4 = matrix(ZZ, 2, 2, [91, -18, 86, -17])
        sage: m5 = matrix(ZZ, 2, 2, [-30, -53, 47, 83])
        sage: m6 = matrix(ZZ, 2, 2, [11, -29, -25, 66])
        sage: m7 = matrix(ZZ, 2, 2, [31, 9, -69, -20])
        sage: matrices = [m0, m1, m2, m3, m4, m5, m6, m7]

        sage: b0 = matrix(ZZ, 2, 2, [1, 0, 0, 2])
        sage: b1 = matrix(ZZ, 2, 2, [2, 0, 0, 1])
        sage: b2 = matrix(ZZ, 2, 2, [2, 0, 1, 1])
        sage: b3 = matrix(ZZ, 2, 2, [2, 0, 1, 3])

        sage: for m in matrices:
        ....:    assert lattice((m * b0).rows()) == (1, 0, 2)
        ....:    assert lattice((m * b1).rows()) == (2, 0, 1)
        ....:    assert lattice((m * b2).rows()) == (2, 1, 1)
        ....:    assert lattice((m * b3).rows()) == (2, 1, 3)

        sage: lattice(sum(((m * b0).rows() for m in matrices), []))
        (1, 0, 2)
        sage: lattice(sum(((m * b1).rows() for m in matrices), []))
        (2, 0, 1)
        sage: lattice(sum(((m * b2).rows() for m in matrices), []))
        (2, 1, 1)
        sage: lattice(sum(((m * b3).rows() for m in matrices), []))
        (2, 1, 3)
    """
    cdef Integer zero = smallInteger(0)
    cdef Integer one = smallInteger(1)
    cdef Integer a, m
    cdef int i, n = len(vectors)
    cdef list w

    if isinstance(vectors, list):
        w = vectors[:]
    else:
        w = list(vectors)

    # Clean the list w of vectors and remove the (0, 0) entries,
    # at the same time looking for vectors of the form (a, 0).
    a = zero
    i = 0
    while i < len(w):
        if not isinstance(w[i][0], Integer) or not isinstance(w[i][1], Integer):
            w[i] = (Integer(w[i][0]), Integer(w[i][1]))
        elif not isinstance(w[i], tuple):
            w[i] = (w[i][0], w[i][1])

        if w[i][1] == zero:
            if w[i][0] == zero:
                del w[i]
            else:
                a = a.gcd(w[i][0])
                i += 1
        else:
            i += 1

    # If there was no horizontal vector, naively build one
    i = 1
    while a == zero and i < n:
        m = w[0][1].lcm(w[i][1])
        a = w[0][0] * (m//w[0][1]) - w[i][0] * (m//w[i][1])
        i += 1
    if a == zero:
        raise ValueError("all input vectors are collinear")

    # Now proceed through a simple reduction algorithm
    w = sorted(set((h, t % a) if h >= 0 else (-h, (-t) % a) for (t, h) in w))
    while len(w) > 1:
        while w[0][0] == zero:
            a = a.gcd(w[0][1])
            del w[0]
            w[0] = (w[0][0], w[0][1] % a)
        if a == one:
            return (one, zero, GCD_list([x for x, y in w]))
        for i in range(1, len(w)):
            w[i] = ((w[i][0] % w[0][0],
                    (w[i][1]-(w[i][0]//w[0][0])*w[0][1]) % a))
        w = sorted(set(w))
    return (a, w[0][1], w[0][0])


cdef inline tuple array_to_tuple(int * x, unsigned int n):
# This approach raises a SIGSEGV error
#    cdef int i
#    cdef tuple res = PyTuple_New(<Py_ssize_t> n)
#
#    for i from 0 <= i < n:
#        PyTuple_SetItem(res, i, PyInt_FromLong(x[i]))
#
#    return res
    cdef list res = [None]*n
    cdef int i

    for i from 0 <= i < n:
        res[i] = x[i]

    return tuple(res)

cdef inline tuple array_to_tuple_i(int * x, unsigned int n):
# This approach raises a SIGSEGV error
#    cdef int i
#    cdef tuple res = PyTuple_New(<Py_ssize_t> n)
#
#    for i from 0 <= i < n:
#        PyTuple_SetItem(res, x[i], PyInt_FromLong(i))
#
#    return res
    cdef list res = [None]*n
    cdef int i

    for i from 0 <= i < n:
        res[x[i]] = i

    return tuple(res)

cdef tuple projectivize_edges(l):
    r"""
    Action of `PGL(2,\ZZ)` or `PSL(2,\ZZ)` knowing the one of `GL(2,\ZZ)` or
    `SL(2,\ZZ)`.

    INPUT:

    - ``l`` - a list of dictionaries
    """
    cdef int i
    cdef Origami_dense_pyx o1, oo1, o2, oo2, ooo
    cdef dict ll
    cdef set waiting
    cdef int *xx
    cdef int *yy

    o1 = next(iterkeys(l[0])) # pick a random element in l[0] !!!!
    oo1 = o1.inverse()
    oo1._set_standard_form()

    # Case 1 (trivial): -Id preserves o (hence preserves each orbit point)
    # (remark: this case corresponds to orientation cover orbits)
    if o1 == oo1:
        return l

    # Case 2: -Id does not preserve the orbit
    elif oo1 not in l[0]:
        ll = [{} for _ in range(len(l))]
        for o in l[0]:
            o1 = o
            o2 = o.inverse()
            o2._set_standard_form()
            if o2 < o1:
                o = o2

            for i in range(len(l)):
                oo1 = l[i][o1]
                oo2 = oo1.inverse()
                oo2._set_standard_form()
                if oo1 < oo2:
                    ll[i][o] = oo1
                else:
                    ll[i][o] == oo2

    # Case 3: -Id preserves the orbit (but not pointwise)
    ll = [{} for _ in range(len(l))]
    waiting = set(l[0])
    while waiting:
        o1 = waiting.pop()
        o2 = o1.inverse()
        o2._set_standard_form()
        waiting.remove(o2)
        if o2 < o1:
            ooo = o1
            o1 = o2
            o2 = ooo

        for i in range(len(l)):
            oo1 = l[i][o1]
            oo2 = l[i][o2]
            if oo1 < oo2:
                ll[i][o1] = oo1
            else:
                ll[i][o1] = oo2

    return ll

def sl_orbit_from_gl_orbit(o, L, I):
    r"""
    Compute the SL(2, Z) orbit of the origami ``o`` knowing the action of GL(2, Z).

    TODO: this has nothing to do with origamis... but rather with the action of
    SL(2, Z), GL(2, Z) and PSL(2, Z).

    INPUT:

    - ``o`` - an origami

    - ``L`` - the action of the matrix l

    - ``I`` - the action of the matrix i

    EXAMPLES:

    On the following example, the SL(2, Z) action has two orbits whereas the
    GL(2, Z) action has only one::

        sage: l_edges = {0:1,1:9,2:8,3:0,4:13,5:10,6:5,7:4,8:11,9:3,10:6,11:2,12:7,13:12}
        sage: i_edges = {0:10,1:5,2:12,3:4,4:3,5:1,6:11,7:9,8:13,9:7,10:0,11:6,12:2,13:8}
        sage: from surface_dynamics.flat_surfaces.origamis.origami_dense import sl_orbit_from_gl_orbit
        sage: s0 = sl_orbit_from_gl_orbit(0, l_edges, i_edges)
        sage: print(sorted(s0[0].items()))
        [(0, 1), (1, 9), (2, 8), (3, 0), (8, 11), (9, 3), (11, 2)]
        sage: print(sorted(s0[1].items()))
        [(0, 11), (1, 0), (2, 9), (3, 8), (8, 2), (9, 3), (11, 1)]
        sage: print(sorted(s0[2].items()))
        [(0, 2), (1, 8), (2, 0), (3, 9), (8, 1), (9, 3), (11, 11)]
        sage: s4 = sl_orbit_from_gl_orbit(4, l_edges, i_edges)
        sage: print(sorted(s4[0].items()))
        [(4, 13), (5, 10), (6, 5), (7, 4), (10, 6), (12, 7), (13, 12)]
        sage: print(sorted(s4[1].items()))
        [(4, 10), (5, 7), (6, 12), (7, 4), (10, 5), (12, 13), (13, 6)]
        sage: print(sorted(s4[2].items()))
        [(4, 7), (5, 13), (6, 6), (7, 4), (10, 12), (12, 10), (13, 5)]
        sage: s1 = sl_orbit_from_gl_orbit(1, l_edges, i_edges)
        sage: s0 == s1, s0 == s4
        (True, False)
    """
    l = {}
    r = {}
    s = {}
    ri = {}

    waiting = set([o])

    while True:
        # Compute L orbit and put new guys in waiting
        ooo = o
        oo = L[o]
        while oo != o:
            l[ooo] = oo
            waiting.add(oo)
            ooo = oo
            oo = L[oo]
        l[ooo] = oo

        # Compute R images while nobody new is found
        while waiting:
            oo = waiting.pop()
            o = I[L[I[oo]]]
            r[oo] = o
            ri[o] = oo
            if not (o in l):
                waiting.add(o)
                break
        else:
            break

    for o in l: # ~s = l ~r l
        s[l[ri[l[o]]]] = o

    return l, r, s

def origami_unpickle(r, u, pos, name):
    o = Origami_dense_pyx(r, u)
    if pos is not None: o.set_positions(pos)
    if name is not None: o.rename(name)
    return o

cdef class Origami_dense_pyx(object):
    r"""
    Origami or square-tiled surface.

    An origami is a flat surface which is a covering of a once-punctured torus.
    It can be described either by a pair of permutations, up to simultaneous
    conjugacy in the symmetric group, or by a subgroup of finite index of the
    free group on two generators, which is the fundamental group of the
    once-punctured torus.

    EXAMPLES::

        sage: from surface_dynamics.all import Origami
        sage: Origami([2, 1, 3], [3, 2, 1])
        (1,2)(3)
        (1,3)(2)
    """
    #
    # Intialize and copy
    #

    def __cinit__(self):
        r"""
        TESTS::

            sage: from surface_dynamics.all import Origami, origamis
            sage: o = Origami('(1,2)', '(1,3)')
            sage: loads(dumps(o)) == o
            True

            sage: E = origamis.EierlegendeWollmilchsau()
            sage: F = loads(dumps(E))
            sage: F
            Eierlegende Wollmilchsau
            sage: F.r()
            (1,2,3,4)(5,6,7,8)
            sage: F.u()
            (1,5,3,7)(2,8,4,6)
        """
        self._n = 0
        self._r = NULL
        self._u = NULL

    def __init__(self, tuple r, tuple u):
        r"""
        TESTS::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami([2, 1, 3], [1, 3, 2])
            sage: o == loads(dumps(o))
            True
        """
        cdef int i

        self._n = len(r)
        self._r = <int *> malloc(2*self._n*sizeof(int))
        self._u = self._r + self._n

        if self._r == NULL:
            raise MemoryError("not able to allocate")

        for i from 0 <= i < self._n:
            self._r[i] = r[i]
            self._u[i] = u[i]

        self._l_edges = {}
        self._i_edges = {}

    def __dealloc__(self):
        if self._r != NULL: free(self._r)

    cdef Origami_dense_pyx _new_c(self, int * rr_and_uu):
        r"""
        Return an origami with given permutations.

        Beware that we assume that the created origami is in the same orbit
        under the action of GL(2, Z).
        """
        cdef Origami_dense_pyx other = Origami_dense_pyx.__new__(Origami_dense_pyx)

        other._n = self._n
        other._r = rr_and_uu
        other._u = rr_and_uu + self._n

        other._l_edges = self._l_edges
        other._i_edges = self._i_edges

        return other

    def __copy__(self):
        r"""
        Return a copy of the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: oo = copy(o)
            sage: o == oo
            True
            sage: o is oo
            False
        """
        cdef int * r = <int *> malloc(2 * self._n * sizeof(int))
        cdef int * u = r + self._n

        memcpy(r, self._r, 2 * self._n * sizeof(int))

        return self._new_c(r)

    def __reduce__(self):
        r"""
        Pickling

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: loads(dumps(o)) == o
            True
        """
        return origami_unpickle, (
                # r, u
                self.r_tuple(), self.u_tuple(),
                # positions, name
                self._pos, self._name)

    #
    # Comparisons
    #

    def __richcmp__(self, other, i):
        r"""
        Comparison

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

        First compare the number of squares::

            sage: o1 = Origami('(1,2)', '(2,1)')
            sage: o2 = Origami('(1,2,3)', '(1,3)')
            sage: o1 == o2
            False
            sage: o1 != o2
            True
            sage: (o1 < o2) and (o1 <= o2)
            True
            sage: (o1 > o2) or (o1 >= o2)
            False

        Then compare the permutations::

            sage: o1 = Origami('(1,2)', '(1,2,3)')
            sage: o2 = Origami('(1,2)', '(1,3,2)')
            sage: o1 == o2
            False
            sage: o1 != o2
            True
            sage: (o1 < o2) and (o1 <= o2)
            True
            sage: (o1 > o2) or (o1 >= o2)
            False
        """
        # 0: <
        # 1: <=
        # 2: ==
        # 3: !=
        # 4: >
        # 5: >=
        cdef Origami_dense_pyx s = <Origami_dense_pyx?> self
        cdef Origami_dense_pyx o = <Origami_dense_pyx?> other

        # Compare number of squares
        if s._n != o._n:
            if i < 2: return s._n < o._n
            if i > 3: return s._n > o._n
            return i == 3

        # Find first index where they differ and take the difference
        test = origami_diff(s._r, o._r, s._n)

        if test == 0: # equality
            return i != 0 and i != 3 and i != 4
        else: # different
            if i < 2: return test < 0
            if i > 3: return test > 0
            return i == 3

    def __hash__(self):
        r"""
        Hash value for self.

        TESTS::

            sage: from surface_dynamics.all import Origami
            sage: h = []
            sage: from itertools import permutations
            sage: for p in permutations(range(5)):
            ....:     for q in permutations(range(5)):
            ....:         h.append(hash(Origami(p, q, as_tuple=True, check=False)))
            sage: len(h) == len(set(h))
            True
        """
        cdef int i, h = 0, br = 12, bu = 37

        for i in range(self._n):
            h += self._r[i]*br + self._u[i]*bu
            br *= 503
            bu *= 251

        return h

    #
    # Python access to attributes
    #

    def nb_squares(self):
        r"""
        Return the number of squares.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.nb_squares()
            3
        """
        return self._n

    def r_tuple(self):
        r"""
        Return the right permutation of the origami as a tuple on {0, ..., n-1}

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.r_tuple()
            (1, 0, 2)
        """
        cdef int i
        return tuple(self._r[i] for i in range(self._n))

    def r_inv_tuple(self):
        r"""
        Return the inverse of the right permutation as a tuple on {0, ..., n-1}

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3)', '(1,2)')
            sage: o.r_inv_tuple()
            (2, 0, 1)
        """
        return array_to_tuple_i(self._r, self._n)

    def u_tuple(self):
        r"""
        Return the up permutation of the origami as a tuple on {0, ..., n-1}

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.u_tuple()
            (2, 1, 0)
        """
        cdef int i
        return tuple(self._u[i] for i in range(self._n))

    def u_inv_tuple(self):
        r"""
        Return the inverse of the up permutation as a tuple on {0, ..., n-1}

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,2,3)')
            sage: o.u_inv_tuple()
            (2, 0, 1)
        """
        return array_to_tuple_i(self._u, self._n)

    #
    # Attribute access
    #

    def r(self):
        r"""
        Return the right permutation of the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3)', '(1,2)')
            sage: o.r()
            (1,2,3)
        """
        return PermutationConstructor([i+1 for i in self.r_tuple()], check=False)

    def r_inv(self):
        r"""
        Return the inverse of the right permutation of the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3)', '(1,2)')
            sage: o.r_inv()
            (1,3,2)
        """
        return PermutationConstructor([i+1 for i in self.r_inv_tuple()], check=False)

    def u(self):
        r"""
        Return the up permutation of the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,2,3,4)')
            sage: o.u()
            (1,2,3,4)
        """
        return PermutationConstructor([i+1 for i in self.u_tuple()], check=False)

    def u_inv(self):
        r"""
        Return the inverse of the up permutation of the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,2,3,4)')
            sage: o.u_inv()
            (1,4,3,2)
        """
        return PermutationConstructor([i+1 for i in self.u_inv_tuple()], check=False)

    def widths_and_heights(self):
        r"""
        Return the list of widths and heights of cylinder.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: Origami('(1,2)', '(1,3)').widths_and_heights()
            [(1, 1), (2, 1)]
            sage: Origami('(1,2)(3,4)', '(1,3,5)(2,4)').widths_and_heights()
            [(1, 1), (2, 2)]
            sage: Origami('(1,2)', '(1,3,4)').widths_and_heights()
            [(1, 2), (2, 1)]
            sage: Origami('(1,2)(3,4)', '(1,3,5,6)(2,4)').widths_and_heights()
            [(1, 2), (2, 2)]
        """
        cdef int * r = self._r
        cdef int * u = self._u
        cdef int * seen = <int *> malloc(self._n * sizeof(int))
        cdef int w, h, i
        cdef list wh = []

        # Compute the set of squares that are on a top of a cylinder.
        # For each top we pick one square and record the width.
        memset(seen, 0, self._n*sizeof(int))
        for i in range(self._n):
            if r[u[i]] != u[r[i]]:
                i = u[i]
                if not seen[i]:
                    w = 0
                    while seen[i] == 0:
                        seen[i] = 1
                        i = r[i]
                        w += 1
                    wh.append((w, i))

        for j, (w, i) in enumerate(wh):
            h = 1
            i = u[i]
            while seen[i] == 0:
                i = u[i]
                h += 1
            wh[j] = (smallInteger(w), smallInteger(h))

        free(seen)
        return wh

    def period_generators(self):
        r"""
        Return a list of periods that generate the lattice of periods.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,3,6)(2,5,7)(4)', '(1,2,4,3,5,6,7)')
            sage: sorted(o.period_generators())
            [(-1, 2), (0, 1), (0, 2), (1, 0), (1, 0), (2, 0)]

            sage: r = '(1,17,6,18,10,8)(11,2,12,5,7,9)(15,16)(3,13)(14,4)'
            sage: u = '(1,11,15,3,14,10,7,6,12)(17,2,16,13,4,8,9,18,5)'
            sage: o = Origami(r, u)
            sage: o.stratum()
            H_2(2)
            sage: sorted(o.period_generators())
            [(-2, 2), (0, 2), (0, 3), (2, 0), (2, 0), (4, 0)]

            sage: o = Origami('(1)(2)(3)', '(1,2,3)')
            sage: o.period_generators()
            [(1, 0), (0, 3)]

            sage: o = Origami('(1,2)', '(1,2)')
            sage: o.period_generators()
            [(2, 0), (-1, 1)]
        """
        # TODO: it is stupid as we do twice the job... in standard form we
        # already compute the singularities
        cdef int * r = self._r
        cdef int * u = self._u
        cdef int * memory = <int *> malloc(2*self._n * sizeof(int))
        cdef int * br_sg = memory
        cdef int * i_to_tr = memory + self._n
        # array: i-> distance to the nearest square that has a singularity in
        # its top right corner on the left (or -1 if none)
        cdef int i, k
        cdef list periods = []
        cdef int mark

        # Compute the set squares which have a singularity in either their top
        # right or bottom right corners
        mark = 0
        memset(br_sg, 0, self._n*sizeof(int))
        for i in range(self._n):
            if r[u[i]] != u[r[i]]:
                mark = 1
                br_sg[u[i]] = 1

        if mark == 0:  # torus case... no marked point
            br_sg[0] = 1

        # Now compute the horizontal saddles and build i_to_tr
        memset(i_to_tr, -1, self._n*sizeof(int))
        for i in range(self._n):
            if br_sg[u[i]]:
                i_to_tr[i] = 0
                j = r[i]
                k = 1
                while br_sg[u[j]] == 0:
                    i_to_tr[j] = k
                    j = r[j]
                    k += 1
                periods.append((smallInteger(k), smallInteger(0)))


        # Now compute the period of vertical transversals in each cylinder
        for i in range(self._n):
            if br_sg[i] == 1:
                j = i
                k = 1
                while i_to_tr[j] == -1:
                    j = u[j]
                    k += 1
                periods.append((smallInteger(-i_to_tr[j]), k))

        if len(periods) == 0:
            raise RuntimeError("this should not happen!")

        free(memory)
        return periods

    def lattice_of_periods(self):
        r"""
        Return `(a, t, u)` for a standard basis of the lattice of periods.

        The corresponding standard basis is `((a, 0), (t, u))`.

        The lattice of periods of an origami is the sublattice of ZZ^2
        generated by the holonomy vectors of its saddle connections. Any
        sublattice of ZZ^2 has a standard basis consisting of a horizontal
        vector `(a, 0)` and a nonhorizontal vector `(t, u)`, where
        `a`, `t`, `u` are integers satisfying ``0 <= t < a`` and ``0 < t``.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.lattice_of_periods()
            (1, 0, 1)
            sage: r = '(1,2,3,4,5,6,7,8,9)(10,11,12)(13,14,15,16,17,18,19,20,21)'
            sage: u = '(1,14,21,19,8,3,10,5,12,4,11,6)(2,15,16,17,18,7)(9,13,20)'
            sage: oy = Origami(r, u)
            sage: oy.lattice_of_periods()
            (3, 2, 1)
        """
        return lattice(self.period_generators())

    def lattice_of_absolute_periods(self):
        r"""
        Return `(a, t, u)` for a standard basis of the lattice of absolute periods.

        The corresponding standard basis is `((a, 0), (t, u))`.

        The lattice of absolute periods of an origami is the sublattice of
        ZZ^2 generated by the holonomy vectors of loops drawn on it. Any
        sublattice of ZZ^2 has a standard basis consisting of a horizontal
        vector `(a, 0)` and a nonhorizontal vector `(t, u)`, where
        `a`, `t`, `u` are integers satisfying ``0 <= t < a`` and ``0 < t``.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)(3,4)', '(2,3)')
            sage: o.lattice_of_absolute_periods()
            (2, 0, 1)

            sage: r = '(1,2)(3,4)(5,6)(7,8,9,10)(11,12)'
            sage: u = '(1,3,5,7)(2,4,6,8)(9,11,10,12)'
            sage: o = Origami(r, u)
            sage: o.lattice_of_absolute_periods()
            (2, 1, 2)
        """
        return lattice(self.absolute_period_generators())

    def optimal_degree(self):
        r"""
        Return the optimal degree of this origami.

        The optimal degree of self is the degree of the map to the largest
        torus.

        Any origami `X \to T` factors as `i \circ \pi_{opt}` where `i` is an
        isogeny. The optimal degree is the degree of `\pi_{opt}`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis
            sage: E = origamis.EierlegendeWollmilchsau()
            sage: E.optimal_degree()
            2

            sage: o = Origami('(1,2)(3,4)', '(2,3)')
            sage: o.optimal_degree()
            2

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.optimal_degree()
            3
        """
        a, t, u = self.lattice_of_absolute_periods()
        return self.nb_squares() / (a*u)

    def is_reduced(self):
        r"""
        Test of reducibility

        An origami is reduced, if it is not a ramified cover of a bigger torus
        with only one ramification point. In other terms, it is equivalent to
        say that the period of the origami generates `\ZZ^2`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_reduced()
            True
            sage: o = Origami('(1,2,3,4)(5,6)', '(1,5)(2,6)')
            sage: o.is_reduced()
            False
            sage: o = Origami('(1,2)(3,4)', '(1,3,5,6)(2,4)')
            sage: o.is_reduced()
            False
            sage: o = Origami('(1,2,3,4)(5,6)', '(1,5)(2,6)')
            sage: o.is_reduced()
            False
            sage: o = Origami('(1,2,3,4)(5,6)', '(1,5)(2,6)')
            sage: o.is_reduced()
            False

            sage: o = Origami('(1)(2)(3)', '(1,2,3)')
            sage: o.is_reduced()
            False
        """
        return self.lattice_of_periods() == (smallInteger(1), smallInteger(0), smallInteger(1))

    #
    # standard form (canonic labels)
    #

    cpdef _set_standard_form(self, return_map=False):
        r"""
        Return the origami renumbered to its standard form.

        Optionally, return the renumbering map too.

        INPUT:

        - ``return_map`` - boolean (default: False)

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami((1, 0, 2), (0, 2, 1), as_tuple=True)
            sage: o.r_tuple()
            (1, 0, 2)
            sage: o.u_tuple()
            (0, 2, 1)
            sage: o._set_standard_form()
            sage: o.r_tuple()
            (0, 2, 1)
            sage: o.u_tuple()
            (1, 0, 2)

        Checking the relabeling map::

            sage: o = Origami('(1,5,3,7,8)(2,9)', '(1,4,6,8)(3,2,7)')
            sage: r = o.r_tuple()
            sage: u = o.u_tuple()
            sage: lab = o._set_standard_form(True)
            sage: rr = o.r_tuple()
            sage: uu = o.u_tuple()
            sage: [rr[lab[i]] for i in range(9)] == [lab[r[i]] for i in range(9)]
            True
            sage: [uu[lab[i]] for i in range(9)] == [lab[u[i]] for i in range(9)]
            True
        """
        cdef int *ren = <int *> malloc(self._n * sizeof(int))
        m = None

        if self._n != 1:
            origami_normal_form(self._r, self._u, ren, self._n)

        if return_map:
            m = array_to_tuple(ren, self._n)

        free(ren)
        return m

    # TODO: compute at the same time the lattice generated by the holonomies
    def to_standard_form(self, return_map=False):
        r"""
        Return an isomorphic origami in standard form.

        INPUT:

        - ``return_map`` - boolean (default: False) - if True return the
          associated mapping

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: oo, m = o.to_standard_form(return_map=True)
            sage: ~m * o.r() * m == oo.r()
            True
            sage: ~m * o.u() * m == oo.u()
            True


            sage: o = Origami('(1,2,3)(4,5)(6)(7,8,9)', '(1,6,3,8,2,7,4,9)')
            sage: oo, m = o.to_standard_form(return_map=True)
            sage: ~m * o.r() * m == oo.r()
            True
            sage: ~m * o.u() * m == oo.u()
            True
        """
        oo = self.__copy__()

        if return_map:
            m = oo._set_standard_form(return_map=True)
            return oo, PermutationConstructor([i+1 for i in m])
        else:
            oo._set_standard_form(return_map=False)
            return oo

    def relabel(self, return_map=False, inplace=False):
        r"""
        Relabel self

        INPUT:

        - ``return_map`` -- return the labelization

        - ``inplace`` -- modify self, the default is False. It might be
          dangerous to set it True as an origami is hashable and
          the hash is modified by this operation.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2,3,4)(5,6)', '(2,5)(3,6)')
            sage: o2, p = o.relabel(return_map=True)
            sage: old_r = o.r_tuple()
            sage: old_u = o.u_tuple()
            sage: new_r = o2.r_tuple()
            sage: new_u = o2.u_tuple()
            sage: all(p[old_r[i]] == new_r[p[i]] for i in range(6))
            True
            sage: all(p[old_u[i]] == new_u[p[i]] for i in range(6))
            True
        """
        if inplace:
            return self._set_standard_form(return_map=return_map)

        else:
            o = self.__copy__()
            m = o._set_standard_form(return_map=return_map)
            if return_map:
                return o, m
            return o

    #
    # GL(2, Z), SL(2, Z), PGL(2, Z) and PSL(2, Z) actions
    #

    cpdef inverse(self):
        r"""
        Return the origami `(r^{-1}, u^{-1})` which corresponds to the action of
        `-Id` on the origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2,3)', '(1,2)')
            sage: o
            (1,2,3)
            (1,2)(3)
            sage: o.inverse()
            (1,3,2)
            (1,2)(3)

        We check that it commutes with the inverse operation on cylinder
        diagrams::

            sage: o = Origami('(1,2,3)(5,6)', '(1,4)(3,5,7)')
            sage: os = o.inverse(); os
            (1,3,2)(4)(5,6)(7)
            (1,4)(2)(3,7,5)(6)

            sage: c = o.cylinder_diagram()
            sage: cs1 = o.cylinder_diagram().inverse()
            sage: cs2 = os.cylinder_diagram()
            sage: cs1.is_isomorphic(cs2)
            True
        """
        cdef int i
        cdef int *rr = <int *>malloc(2*self._n*sizeof(int))
        cdef int *uu = rr + self._n

        for i from 0 <= i < self._n:
            rr[self._r[i]] = i
            uu[self._u[i]] = i

        return self._new_c(rr)

    def vertical_symmetry(self):
        r"""
        Return the origami `(r^{-1}, u)`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3)(5,6)', '(1,4)(3,5,7)')
            sage: ov = o.vertical_symmetry(); ov
            (1,3,2)(4)(5,6)(7)
            (1,4)(2)(3,5,7)(6)

        We check that it commutes with vertical symmetry of the cylinder
        diagram::

            sage: cv1 = o.cylinder_diagram().vertical_symmetry()
            sage: cv2 = ov.cylinder_diagram()
            sage: cv1.is_isomorphic(cv2)
            True
        """
        cdef int i
        cdef int * rr = <int *> malloc(2*self._n*sizeof(int))
        cdef int * uu = rr + self._n

        for i in range(self._n):
            rr[self._r[i]] = i
            uu[i] = self._u[i]

        return self._new_c(rr)

    def horizontal_symmetry(self):
        r"""
        Return the origami `(r, u^{-1})`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3)(5,6)', '(1,4)(3,5,7)')
            sage: oh = o.horizontal_symmetry(); oh
            (1,2,3)(4)(5,6)(7)
            (1,4)(2)(3,7,5)(6)

        We check that it commutes with vertical symmetry of the cylinder
        diagram::

            sage: ch1 = o.cylinder_diagram().horizontal_symmetry()
            sage: ch2 = oh.cylinder_diagram()
            sage: ch1.is_isomorphic(ch2)
            True
        """
        cdef int i
        cdef int * rr = <int *> malloc(2*self._n*sizeof(int))
        cdef int * uu = rr + self._n

        for i in range(self._n):
            rr[i] = self._r[i]
            uu[self._u[i]] = i

        return self._new_c(rr)

    cpdef mirror(self):
        r"""
        Return the origami (u, r)

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2,3,4)', '(1,5)'); o
            (1,2,3,4)(5)
            (1,5)(2)(3)(4)
            sage: o.mirror()
            (1,5)(2)(3)(4)
            (1,2,3,4)(5)
        """
        cdef int i
        cdef int *rr = <int *>malloc(2*self._n*sizeof(int))
        cdef int *uu = rr + self._n

        for i in range(self._n):
            rr[i] = self._u[i]
            uu[i] = self._r[i]

        return self._new_c(rr)

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

            sage: from surface_dynamics import Origami
            sage: Origami('(1,2)', '(1,3)').horizontal_cycle_representatives()
            [0, 2]
        """
        cdef list l = []
        cdef int * seen  = <int *> calloc(self._n, sizeof(int))
        cdef int i

        for i in range(self._n):
            if seen[i]:
                continue
            l.append(i)
            while not seen[i]:
                seen[i] = 1
                i = self._r[i]

        free(seen)
        return l

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

            sage: from surface_dynamics import Origami
            sage: Origami('(1,2)', '(1,3)').vertical_cycle_representatives()
            [0, 1]
        """
        cdef list l = []
        cdef int * seen = <int *> calloc(self._n, sizeof(int))
        cdef int i

        for i in range(self._n):
            if seen[i]:
                continue
            l.append(i)
            while not seen[i]:
                seen[i] = 1
                i = self._u[i]

        free(seen)
        return l

    cpdef horizontal_twist(self, width=1, cylinder=None):
        r"""
        Return the origami ``(r, r^{-width}u)`` which is obtained by the action of an
        horizontal twist of width ``k`` on this origami.

        INPUT:

        - ``width`` - integer (default: 1) - the width of the twist

        - ``cylinder`` - integer or ``None`` (default ``None``) - if not ``None`` performs
          a twist only in the band of squares that contain ``i``. In that case, it is
          not a SL(2,R)-deformation.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3,4,5,6)', '(1,7)')
            sage: o
            (1,2,3,4,5,6)(7)
            (1,7)(2)(3)(4)(5)(6)
            sage: o.horizontal_twist()
            (1,2,3,4,5,6)(7)
            (1,6,5,4,3,2,7)
            sage: o.horizontal_twist(-1)
            (1,2,3,4,5,6)(7)
            (1,2,3,4,5,6,7)
            sage: o.horizontal_twist(6) == o
            True

            sage: S = SymmetricGroup(5)
            sage: r = S('(1,2,3)(4,5)')
            sage: u = S('(1,4)(2,5)')
            sage: o = Origami(r, u)
            sage: o.horizontal_twist(-1, 1) == o.horizontal_twist(2, 1) == Origami(r, S('(1,2,3)') * u)
            True
            sage: o.horizontal_twist(-2, 1) == o.horizontal_twist(1, 1) == Origami(r, S('(1,2,3)')^2 * u)
            True
            sage: o.horizontal_twist(-3, 1) == o.horizontal_twist(-3, 1) == o
            True
        """
        cdef int i0, i, ii
        cdef int *rr = <int *>malloc(2*self._n*sizeof(int))
        cdef int *uu = rr + self._n

        if cylinder is not None:
            i0 = int(cylinder)
            if i0 < 0 or i0 >= self._n:
                raise ValueError("square number out of range")
            for i from 0 <= i < self._n:
                rr[i] = self._r[i]
                uu[i] = self._u[i]
            i = i0
            if width <= 0:
                ii = i
                for j from 0 <= j < -width:
                    ii = self._r[ii]
            else:
                ii = self._r[i]
                for j from 1 <= j < width:
                    ii = self._r[ii]
            while True:
                if width <= 0:
                    uu[i] = self._u[ii]
                else:
                    uu[ii] = self._u[i]
                i = self._r[i]
                ii = self._r[ii]
                if i == i0:
                    break
        else:
            for i from 0 <= i < self._n:
                rr[i] = self._r[i]
                if width <= 0:
                    ii = i
                    for j from 0 <= j < -width:
                        ii = self._r[ii]
                    uu[i] = self._u[ii]
                else:
                    ii = self._r[i]
                    for j from 1 <= j < width:
                        ii = self._r[ii]
                    uu[ii] = self._u[i]

        return self._new_c(rr)

    cpdef vertical_twist(self, width=1, cylinder=None):
        r"""
        Return the origami `(ru^{-k}, u)` obtained by the action of a
        vertical twist of width `k` on this origami.

        INPUT:

        - ``width`` - integer (default: 1) - the width of the twist

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2,3,4)', '(4,5,6,7)')
            sage: o.vertical_twist()
            (1,2,3,4,7,6,5)
            (1)(2)(3)(4,5,6,7)
            sage: o.vertical_twist(-1)
            (1,2,3,4,5,6,7)
            (1)(2)(3)(4,5,6,7)
            sage: o.vertical_twist(4) == o
            True

            sage: S = SymmetricGroup(5)
            sage: r = S('(1,4)(2,5)')
            sage: u = S('(1,2,3)(4,5)')
            sage: o = Origami(r, u)
            sage: o.vertical_twist(-1, 1) == o.vertical_twist(2, 1) == Origami(S('(1,2,3)') * r, u)
            True
            sage: o.vertical_twist(-2, 1) == o.vertical_twist(1, 1) == Origami(S('(1,2,3)')^2 * r, u)
            True
            sage: o.vertical_twist(-3, 1) == o.vertical_twist(-3, 1) == o
            True
        """
        cdef int i
        cdef int *rr = <int *>malloc(2*self._n*sizeof(int))
        cdef int *uu = rr + self._n


        if cylinder is not None:
            i0 = int(cylinder)
            if i0 < 0 or i0 >= self._n:
                raise ValueError("square number out of range")
            for i from 0 <= i < self._n:
                rr[i] = self._r[i]
                uu[i] = self._u[i]
            i = i0
            if width <= 0:
                ii = i
                for j from 0 <= j < -width:
                    ii = self._u[ii]
            else:
                ii = self._u[i]
                for j from 1 <= j < width:
                    ii = self._u[ii]
            while True:
                if width <= 0:
                    rr[i] = self._r[ii]
                else:
                    rr[ii] = self._r[i]
                i = self._u[i]
                ii = self._u[ii]
                if i == i0:
                    break
        else:
            for i from 0 <= i < self._n:
                uu[i] = self._u[i]
                if width <= 0:
                    ii = i
                    for j from 0 <= j < -width:
                        ii = self._u[ii]
                    rr[i] = self._r[ii]
                else:
                    ii = self._u[i]
                    for j from 1 <= j < width:
                        ii = self._u[ii]
                    rr[ii] = self._r[i]

        return self._new_c(rr)

    cdef _compute_gl2z_edges(self):
        r"""
        Compute the action of `GL(2, \ZZ)`.

        The generators of `GL(2, ZZ)` considered are

        I =
        [0 1]
        [1 0]

        L =
        [1 1]
        [0 1]

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami((1, 0, 2), (0, 2, 1), as_tuple=True)
            sage: l, i = o.gl2z_edges() # indirect doctest
            sage: len(l), len(i)
            (3, 3)
        """
        cdef dict l_edges = self._l_edges
        cdef dict i_edges = self._i_edges
        cdef bool VERBOSE=False
        cdef int i, n=self._n
        cdef size_t N = 2*self._n*sizeof(int)
        cdef set waiting = set([])
        cdef int * renum = <int *> malloc(self._n * sizeof(int))
        cdef int *r = <int *> malloc(N)
        cdef int *u = r+n
        cdef int * rr = <int *> malloc(N)
        cdef int * uu = rr+n
        cdef int *rrr
        cdef int *uuu
        cdef Origami_dense_pyx o, oo, ooo

        # set o = self and put o in normal form
        for i from 0 <= i < n:
            r[i] = self._r[i]
            u[i] = self._u[i]
        origami_normal_form(r, u, renum, n)
        o = self._new_c(r)
        waiting.add(o)

        # at each step, o, r, u is set to the current origami
        # and rr, uu is memory allocated
        while True:
            if VERBOSE:
                print("loop from %s %s" % (str(o.r_tuple()), str(o.u_tuple())))
                print("len(l) = %d len(i) = %d" % (len(l_edges), len(i_edges)))
            if o in l_edges:
                raise ValueError("%s seen before" % str(o))

            # we compute backwards the l-cusp of o
            for i from 0 <= i < n:
                rr[i] = r[i]
                uu[i] = r[u[i]]
            origami_normal_form(rr, uu, renum, n)

            ooo = o
            while origami_diff(r, rr, n):
                oo = o._new_c(rr)
                if VERBOSE:
                    print("new element in cups %s %s" % (str(oo.r_tuple()), str(oo.u_tuple())))
                waiting.add(oo)
                l_edges[oo] = ooo
                ooo = oo
                rrr = rr
                uuu = uu
                rr = <int *>malloc(N)
                uu = rr+n
                for i from 0 <= i < n:
                    rr[i] = rrr[i]
                    uu[i] = rrr[uuu[i]]
                origami_normal_form(rr, uu, renum, n)

            l_edges[o] = ooo

            if VERBOSE:
                print("cups computed")
                print("len(l) = %d len(i) = %d" % (len(l_edges), len(i_edges)))
                print("%d origami wait" % len(waiting))

            # then we create i-edges until we find a new guy
            # we set r, u to be available
            r = rr; u = uu
            while waiting:
                oo = waiting.pop()
                if VERBOSE:
                    print("try i-link from %s %s" % (str(oo.r_tuple()), str(oo.u_tuple())))
                rr = oo._r; uu = oo._u
                for i from 0 <= i < n:
                    r[i] = uu[i]
                    u[i] = rr[i]
                origami_normal_form(r, u, renum, n)
                if origami_diff(r, rr, n): # not symmetric under r <-> u
                    o = self._new_c(r)
                    if VERBOSE:
                        print("find new guy %s %s" % (str(o.r_tuple()), str(o.u_tuple())))
                    rr = <int *>malloc(N)
                    uu = rr+n
                    i_edges[o] = oo
                    i_edges[oo] = o
                    if o in waiting: # we find a fake new guy
                        if VERBOSE:
                            print("he was there before")
                        waiting.remove(o)
                        r = rr; u = uu
                    else: # we find a real new guy
                        break
                else: # symmetric under r <-> u
                    if VERBOSE:
                        print("symmetric one")
                    i_edges[oo] = oo
                    rr = NULL
                    uu = NULL
            else:
                break

        free(renum)
        free(rr)

    def gl2z_edges(self):
        r"""
        Return the `GL(2,\ZZ)` action on the `GL(2,\ZZ)`-orbit of this origami.

        The action is returned as a pair of dictionaries ``(l_edges, i_edges)``
        representing the action of the following two generators for `GL(2, ZZ)`:

        .. MATH::

            L = \begin{pmatrix}1&1\\0&1\end{pmatrix}
            I = \begin{pmatrix}0&1\\1&0\end{pmatrix}
            \quad \text{and} \quad

        BEWARE: Do not modify these dictionaries!

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: l, i = o.gl2z_edges()
            sage: for oo in sorted(l): print("(%s,%s) -> (%s,%s)" % (oo.r(), oo.u(), l[oo].r(), l[oo].u()))
            ((2,3),(1,2)) -> ((2,3),(1,2,3))
            ((2,3),(1,2,3)) -> ((2,3),(1,2))
            ((1,2,3),(2,3)) -> ((1,2,3),(2,3))

        TESTS::

            sage: o = Origami('(1,2,3,4)', '(1,5)')
            sage: l, i = o.gl2z_edges()
            sage: ll, ii = o.gl2z_edges()
            sage: l is ll
            True
            sage: i is ii
            True
        """
        if not self._l_edges: # empty dictionary means not computed yet
            self._compute_gl2z_edges()

        return self._l_edges, self._i_edges

    def sl2z_edges(self):
        r"""
        Return the ction of SL(2, Z) on the SL(2, Z)-orbit of this origami.

        The action is returned as a triple of dictionaries ``(l, r, s)``
        representing the action of the matrices L, R, S, where:

        L =
        [1 1]
        [0 1]

        R =
        [1 0]
        [1 1]

        S = R * ~L * R = ~L * R * ~L =
        [0 -1]
        [1  0]

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1)(2,3)(4,5)', '(1,2,3,4)(5)')
            sage: l, r, s = o.sl2z_edges()
            sage: len(l), len(r), len(s)
            (12, 12, 12)
            sage: all(l[s[l[s[l[s[o]]]]]] == s[s[o]] for o in l)
            True
        """
        o = self.to_standard_form()
        L, I = self.gl2z_edges()
        return sl_orbit_from_gl_orbit(o, L, I)

    def pgl2z_edges(self):
        r"""
        Return the `PGL(2,\ZZ)`-action on the `PGL(2,\ZZ)`-orbit of this origami.

        The action is returned as a pair of dictionaries ``(l, i)``
        representing the projective action of the matrices:

        L =
        [1 1]
        [0 1]

        and

        I =
        [0 1]
        [1 0]

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: l, i = o.pgl2z_edges()
            sage: len(l), len(i)
            (3, 3)
        """
        return projectivize_edges(self.gl2z_edges())

    def psl2z_edges(self):
        r"""
        Return the `PSL(2,\ZZ)`-action on the `PSL(2,\ZZ)`-orbit of this origami.

        The action is returned as a triple of dictionaries ``(l, r, s)``
        representing the projective action of the matrices:


        L =
        [1 1]
        [0 1]

        R =
        [0 1]
        [1 1]

        and

        S =
        [0 -1]
        [1  0]

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: l, r, s = o.psl2z_edges()
            sage: len(l), len(r), len(s)
            (3, 3, 3)
        """
        return projectivize_edges(self.sl2z_edges())

    #
    # Lyapunov exponents
    #

    def lyapunov_exponents_approx(self,
            nb_iterations=0X5000, nb_experiments=4, only_mean=True,
            seed=None,
            nb_vectors=None, nb_vectors_p=None, nb_vectors_m=None,
            involution=None):
        r"""
        Approximation of Lyapunov exponents of the Kontsevich-Zorich cocycle.

        An origami defines a Teichmuller curve in the moduli space of
        translation surfaces. The Kontsevich-Zorich cocycle above this
        Teichmuller curve (for the Haar measure) has the following form

        .. MATH::

            1 = \lambda_1, \lambda_2, ... \lambda_g, -\lambda_g, ...,
            -\lambda_2, -\lambda_1 = -1

        This function return the approximations of `\lambda_2, \lambda_3, ...,
        \lambda_g)` as a list.

        INPUT:

        - ``nb_iterations`` - integer (default: 2**17) - the number of
          iterations performed in the algorithm

        - ``nb_experiments`` - integer (default: 4) - the number of experiments
          to perform

        - ``only_mean`` - boolean (default: ``True``) - if ``True``, return the
          list of mean exponents, otherwise return a list of lists.

        - ``nb_vectors`` - integer (default: genus-1) - the number of vectors to
          consider

        - ``involution`` - permutation or boolean - if ``True`` or an inan involution for the
          origami with
          derivative either 1.

        - ``nb_vectors_p``, ``nb_vectors_m`` - if involution is not None, then
          it will be interpreted as the number of + and - vectors to consider.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)(3,4)(5,6)', '(2,3)(4,5)')
            sage: lexp = o.lyapunov_exponents_approx(nb_iterations=2**19)
            sage: lexp # abs tol 0.05
            [0.6666, 0.3333]

            sage: o = Origami('(1,2)(3,4)(5,6)(7,8)(9,10)', '(2,3)(4,5)(6,7)(8,9)')
            sage: s = SymmetricGroup(10)('(1,10)(2,9)(3,8)(4,7)(5,6)')
            sage: o.lyapunov_exponents_approx(involution=s, nb_iterations=2**19)  # abs tol 0.05
            ([0.6000, 0.2000],
             [0.8000, 0.4000])
        """
        #TODO: use the seed to init.

        if involution is None:
            if nb_vectors is None:
                nb_vectors = self.genus()-1
            nb_vectors = int(nb_vectors)
            assert nb_vectors >= 0, "nb_vectors should be >= 0"

            return self.pyx_lyapunov_exponents_approx(nb_iterations,
                    nb_experiments, nb_vectors, only_mean)

        else:
            if involution is True:
                A = self.automorphism_group()
                assert A.order() == 2, "The automorphism group is not of order 2"
                involution = A.list()[1]
            else:
                if not isinstance(involution, PermutationGroupElement):
                    involution = PermutationConstructor(involution)

            assert involution * self.r() * involution == self.r(), "srs is different from s"
            assert involution * self.u() * involution == self.u(), "sus is different from u"

            if nb_vectors_p is None or nb_vectors_m is None:
                from sage.groups.perm_gps.permgroup import PermutationGroup
                oo = self.quotient(PermutationGroup([involution]))
                if nb_vectors_p is None:
                    nb_vectors_p = oo.genus()-1
                if nb_vectors_m is None:
                    nb_vectors_m = self.genus()-oo.genus()

            nb_vectors_p = int(nb_vectors_p)
            assert nb_vectors_p >= 0
            nb_vectors_m = int(nb_vectors_m)
            assert nb_vectors_m >= 0

            involution = [x-1 for x in involution.tuple()]
            involution.extend(range(len(involution), self.nb_squares()))

            return self.pyx_lyapunov_exponents_approx_with_involution(
                    involution,
                    nb_iterations, nb_experiments,
                    nb_vectors_p, nb_vectors_m,
                    only_mean)

    cdef pyx_lyapunov_exponents_approx_with_involution(self, involution,
            nb_iterations, nb_experiments,
            nb_vectors_p, nb_vectors_m,
            only_mean):
        import sys
        from time import time
        cdef origami_with_involution_data * o
        cdef double * theta
        n_p = nb_vectors_p
        n_m = nb_vectors_m

        if n_p == 1: n_p=2
        if n_m == 1: n_m=2
        n = n_p + n_m

        res = [[] for _ in range(n)]
        theta = <double *> malloc((n+1)*sizeof(double))
        s = <int *> malloc((self.nb_squares()) * sizeof(int))

        for i in range(self.nb_squares()):
            s[i] = involution[i]

        o = new_origami_with_involution_data(
                self.nb_squares(), # degree
                n_p,               # nb_vectors_p
                n_m,               # nb_vectors_m
                self._r,           # r
                self._u,           # u
                s)                 # involution

        # TODO: use Gauss measure for the random angles and not simply Lebesgue
        # would be better to feed the function with the angles
        from sage.rings.real_mpfr import RealField
        R = RealField()
        for _ in range(nb_experiments):
            lyapunov_exponents_with_involution(o, nb_iterations, theta)
            for i in range(n):
                res[i].append(R(theta[i+1] / (2*theta[0])))

        free_origami_with_involution_data(o)
        free(s)
        free(theta)

        if only_mean:
            rres = []
            for i in range(n):
                rres.append(sum(res[i]) / nb_experiments)

            return rres[:nb_vectors_p], rres[n_p:n_p+nb_vectors_m]

        return res[:nb_vectors_p], res[n_p:n_p+nb_vectors_m]

    cdef pyx_lyapunov_exponents_approx(self,
            nb_iterations, nb_experiments,
            nb_vectors, only_mean):
        import sys
        cdef origami_data * o
        cdef double * theta
        n = max(2, nb_vectors)

        res = [[] for _ in range(n)]
        theta = <double *> malloc((n+1)*sizeof(double))

        o = new_origami_data(
                self.nb_squares(),
                n,
                self._r,
                self._u)

        from sage.rings.real_mpfr import RealField
        R = RealField()
        for _ in range(nb_experiments):
            lyapunov_exponents(o, nb_iterations, theta)
            for i in range(n):
                res[i].append(R(theta[i+1] / (2*theta[0])))

        free_origami_data(o)
        free(theta)

        if only_mean:
            rres = []
            for i in range(n):
                rres.append(sum(res[i]) / nb_experiments)

            return rres[:nb_vectors]

        return res[:nb_vectors]

    def as_graph(self):
        r"""
        Return the graph associated to self

        The graph associated to an origami is the graph on [1, ..., N] for which
        the edges correspond to the action of the permutations `r` and `u`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: G = o.as_graph(); G
            Looped multi-digraph on 3 vertices
            sage: G.vertices()
            [0, 1, 2]
            sage: G.edges()
            [(0, 1, 'r'), (0, 2, 'u'), (1, 0, 'r'), (1, 1, 'u'), (2, 0, 'u'), (2, 2, 'r')]
        """
        from sage.graphs.digraph import DiGraph

        G = DiGraph(multiedges=True, loops=True)
        N = self.nb_squares()
        r = self.r_tuple()
        u = self.u_tuple()
        for i in range(N):
            G.add_edge(i, r[i], 'r')
            G.add_edge(i, u[i], 'u')
        return G

    def is_connected(self):
        r"""
        Check whether the origami is connected or not

        It is equivalent to ask whether the group generated by `r` and `u` acts
        transitively on the `\{1, \dots, n\}`.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_connected()
            True
            sage: o = Origami('(1,2)(3,4)', '(1,2)', check=False)
            sage: o.is_connected()
            False
        """
        return self.as_graph().is_connected()

    def connected_components(self):
        r"""
        Return the list of connected origami that composes this origami.
        """
        cc = self.as_graph().connected_components()
        r = self.r_tuple()
        u = self.u_tuple()
        if len(cc) == 1:
            return [self]
        l = []
        for c in cc:
            rr = [None] * len(c)
            uu = [None] * len(c)
            d = dict((c[i], i) for i in range(len(c)))
            for i in c:
                rr[d[i]] = d[r[i]]
                uu[d[i]] = d[u[i]]
            l.append(Origami_dense_pyx(rr, uu))
        return l

    def is_isomorphic(self, Origami_dense_pyx other):
        r"""
        Isomorphism test

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o1 = Origami('(1,2)', '(1,3)')
            sage: o2 = Origami('(1,2)', '(2,3)')
            sage: o3 = Origami('(1,3)', '(1,2)')
            sage: o1.is_isomorphic(o2) and o2.is_isomorphic(o1)
            True
            sage: o1.is_isomorphic(o3) and o3.is_isomorphic(o1)
            True
            sage: o2.is_isomorphic(o3) and o3.is_isomorphic(o2)
            True
        """
        if self._n != other._n:
            return False
        cdef Origami_dense_pyx ss = self.relabel()
        cdef Origami_dense_pyx oo = other.relabel()
        cdef size_t i
        for i in range(self._n):
            if ss._r[i] != oo._r[i]:
                return False
            if ss._u[i] != oo._u[i]:
                return False
        return True
    #
    # Component of stratum
    #

    def stratum_component(self, fake_zeros=False, verbose=False):
        r"""
        Return the component of stratum this origami belongs to.

        INPUT:

        - ``fake_zeros`` -- (boolean, default ``False``) whether the stratum
          should include 0 degree order

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.stratum_component()
            H_2(2)^hyp

            sage: r = '(1)(2)(3,4)(5,6,7,8)(9,10)'
            sage: u = '(1,2,3,5,6,10)(4,9)(7,8)'
            sage: Origami(r, u).stratum_component()
            H_4(2^3)^even
            sage: Origami(u, r).stratum_component()
            H_4(2^3)^even

            sage: r = '(1,2,3,4,5)(6,7,8,9,10)'
            sage: u = '(1,6)(2,10)(3,9)(4,8)(5,7)'
            sage: o = Origami(r, u)
            sage: o.stratum_component()
            H_5(4^2)^odd

            sage: r = '(1,2,3,4,5,6,7,8,9,10,11,12,13)'
            sage: u1 = '(1,2,3,4,13)(5,12)(6,11)(7,10)(8,9)'
            sage: u2 = '(1,2,3,4,13)(5,6)(7)(8,9)(10,11)(12)'
            sage: u3 = '(1,2,3,4,13)(5,12,10,9,6,11,8,7)'
            sage: o1 = Origami(r, u1)
            sage: print(o1.stratum_component(), o1.stratum_component(True))
            H_5(4^2)^hyp H_5(4^2, 0^3)^hyp
            sage: o2 = Origami(r, u2)
            sage: print(o2.stratum_component(), o2.stratum_component(True))
            H_5(4^2)^odd H_5(4^2, 0^3)^odd
            sage: o3 = Origami(r, u3)
            sage: print(o3.stratum_component(), o3.stratum_component(True))
            H_5(4^2)^even H_5(4^2, 0^3)^even

        TESTS::

            sage: from surface_dynamics import AbelianStrata
            sage: for d in range(2, 10):
            ....:     for A in AbelianStrata(dimension=d):
            ....:         for C in A.components():
            ....:             o = C.one_origami()
            ....:             assert o.stratum_component() == C
            ....:             o = o.vertical_twist()
            ....:             assert o.stratum_component() == C
            ....:             o = o.horizontal_twist().vertical_twist()
            ....:             assert o.stratum_component() == C
        """
        A = self.stratum(fake_zeros)
        if A.is_connected():
            return A.unique_component()
        cyl = self.cylinder_diagram()
        if cyl.is_hyperelliptic():
            return A.hyperelliptic_component()
        elif any(d % 2 for d in A.zeros()):
            return A.non_hyperelliptic_component()
        elif cyl.spin_parity() == 0:
            return A.even_component()
        else:
            return A.odd_component()

    #
    # Orientation quotient (quad. diff.)
    #

    def is_orientation_cover(self):
        r"""
        Return true if the origami is an orientation cover of a quadratic
        differential.

        It is equivalent to say that `-1` is in the Veech group.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_orientation_cover()
            True

            sage: r = '(1,2,3,4,5,6,7,8,9,10)'
            sage: u = '(3,5,7,9,4,8,10)'
            sage: o = Origami(r, u)
            sage: o.is_orientation_cover()
            False
        """
        return self.relabel() == self.inverse().relabel()

    is_quadratic_cover = is_orientation_cover

    def orientation_data(self, points=False, verbose=0):
        r"""
        Return the list of quadratic stratum and ramification data associated to
        the orientation quotients of this origami. If the origami is primitive,
        then there is at most one orientation quotient.

        Each element of the list is a 3-tuple containing:

        - a quadratic stratum

        - the list of degrees of zeros which are ramified in the covering
          (consider only integer points)

        - the partition of half-integers points which are mapped to poles
          (middle of squares, horizontal edges, vertical edges)

        INPUT:

        - ``points`` - boolean (default: False) - return singularitiy tuples and
          not only degrees

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

        The stratum H(2) contains two families of primitive origamis for an odd
        number of squares. Every surface in H(2) is a covering of a quadratic
        differential in Q(1,-1^5). The ramification data gives an invariant for
        those families::

            sage: o = Origami('(1,2,3,4,5)', '(2,1)')
            sage: o.stratum_component()
            H_2(2)^hyp
            sage: o.orientation_data()
            [(Q_0(1, -1^5), (2,), (1, 3, 1))]
            sage: o = Origami('(1,2,3)', '(1,4,5)')
            sage: o.stratum_component()
            H_2(2)^hyp
            sage: o.orientation_data()
            [(Q_0(1, -1^5), (2, 0, 0), (1, 1, 1))]

            sage: o = Origami('(1)(2)(3,4)(5,6,7)', '(1,2,3)(4,5)(6)(7)')
            sage: o.stratum_component()
            H_3(4)^hyp
            sage: o.orientation_data()
            [(Q_0(3, -1^7), (0, 4, 0), (3, 1, 1))]
            sage: o = Origami('(1)(2)(3)(4,5)(6,7)', '(1,2,3,4)(5,6,7)')
            sage: o.stratum_component()
            H_3(4)^hyp
            sage: o.orientation_data()
            [(Q_0(3, -1^7), (4,), (3, 1, 3))]
            sage: o = Origami('(1)(2)(3)(4,5)(6,7)', '(1,2,3,4)(5,6)(7)')
            sage: o.stratum_component()
            H_3(4)^hyp
            sage: o.orientation_data()
            [(Q_0(3, -1^7), (4,), (5, 1, 1))]

            sage: o = Origami('(1,2,4)(3,6,5)', '(1,3)(2,5)(4,6)')
            sage: for q, _, _ in sorted(o.orientation_data()): print(q)
            Q_1(4, -1^4)
            Q_1(4, -1^4)
            Q_1(4, -1^4)
            Q_0(1^2, -1^6)
        """
        sf1, m1 = self.to_standard_form(return_map=True)
        sf2, m2 = self.inverse().to_standard_form(return_map=True)

        # Being preserved by -Id means there is a non orientable automorphism
        if sf1 != sf2:
            return []

        from surface_dynamics.flat_surfaces.quadratic_strata import QuadraticStratum

        m = m2 * ~m1  # one element which reverses orientation
        if verbose:
            print("m  = %s" % m)
        r = self.r()
        u = self.u()
        assert(m*r*~m == ~r and m*u*~m == ~u) # check

        rot_pi = r*u
        rot_2pi = r*u*~r*~u
        singularities = rot_2pi.cycle_tuples(singletons=True)
        degrees = {}
        for a in map(len, singularities):
            a=a-1
            if a:
                if a in degrees: degrees[a] += 1
                else: degrees[a] = 1

        res = []

        # Consider all orientation reversing involutions
        G = self.automorphism_group()
        for g in G:
            mm = m * g # an automorphism which reverses the orientation
            if mm.order() > 2:
                continue

            if verbose:
                print("g  = %s" % g)
                print("mm = %s" % mm)

            # Fixed points which are not integer points
            squares = []; h_edges = []; v_edges = []
            for i in range(1, self.nb_squares()+1):
                if mm(i) == i: squares.append(i)
                if mm(i) == u(i): h_edges.append(i)
                if mm(i) == r(i): v_edges.append(i)

            # Fixed integer points
            vertices = []
            ramifications = dict((d, 0) for d in degrees)
            ramifications[0] = 0
            for c in singularities:
                if rot_pi(c[len(c)//2]) == mm(c[0]):
                    vertices.append(c)
                    ramifications[len(c)-1] += 1

            if verbose:
                print("ramifications")
                print("  deg. of int. pts.", ramifications)
                print("  h_edges", len(h_edges))
                print("  v_edges", len(v_edges))
                print("  centers", len(squares))

            qdegrees = dict((d-1, ramifications[d]) for d in ramifications)
            qdegrees[-1] += len(squares) + len(h_edges) + len(v_edges)
            qdegrees.update((2*d, (degrees[d]-ramifications[d]) // 2) for d in degrees)

            if points:
                res.append((
                    QuadraticStratum(qdegrees),
                    vertices,
                    (squares, h_edges, v_edges)))
            else:
                res.append((
                    QuadraticStratum(qdegrees),
                    tuple(len(c)-1 for c in vertices),
                    (len(squares), len(h_edges), len(v_edges))))

            if verbose:
                print(" stratum", res[-1][0])
            assert(not res[-1][0].is_empty())  # check that the stratum is non empty!

        return res

    def is_hyperelliptic(self, stratum=False):
        r"""
        Return True if this origami is hyperelliptic

        If ``stratum`` is set to ``True``, then return also the corresponding
        stratum of quadratic differentials this origami is a cover from.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_hyperelliptic()
            True
            sage: o.is_hyperelliptic(stratum=True)
            (True, Q_0(1, -1^5))

            sage: o = origamis.Podium([3, 3, 2, 1])
            sage: o.is_hyperelliptic()
            False
            sage: o.is_hyperelliptic(stratum=True)
            (False, None)
        """
        for q, _, _ in self.orientation_data():
            if q.genus() == 0:
                if stratum:
                    return True, q
                return True

        if stratum:
            return False, None
        return False

    #
    # Quotients and covers
    #

    def reduce(self):
        r"""
        Return a reduced origami isomorphic (up to SL(2, QQ) action) to that origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)(3,4)', '(1,3,5,6)(2,4)')
            sage: o.lattice_of_periods()
            (1, 0, 2)
            sage: o.reduce()
            (1,2)(3)
            (1,3)(2)

            sage: o = Origami('(1,2)(3,4,5,6)', '(1,3,5)(2,4,6)')
            sage: o.lattice_of_periods()
            (2, 0, 1)
            sage: o.reduce()
            (1)(2,3)
            (1,2,3)

            sage: o = Origami('(1,2)(3,4,5,6)', '(1,3,4,5)(2,6)')
            sage: o.lattice_of_periods()
            (2, 1, 1)
            sage: o.reduce()
            (1)(2,3)
            (1,2,3)
        """
        N = self.nb_squares()
        w, t, h = self.lattice_of_periods() # ((w, 0), (t, h))
        vol = h*w
        NN = N//vol

        # two trivial cases
        if NN == 1:
            return Origami_dense_pyx((0,), (0,))
        elif NN == N:
            return self

        # 1. find a square with some singularity in the bot-left corner
        r = self.r()
        u = self.u()
        v = (~r * ~u * r * u).cycle_tuples()

        # 2. then rebuild new permutation with pr and pu
        pr = r**w
        pu = r**t * u**h

        n = 1
        test = [v[0][0]]
        relabel = {v[0][0]:0}
        uu = [None] * NN
        rr = [None] * NN

        while test:
            i = test.pop()
            if i not in relabel:
                relabel[i] = n
                n += 1
            j = pr(i)
            if j not in relabel:
                test.append(j)
                relabel[j] = n
                n += 1
            rr[relabel[i]] = relabel[j]

            j = pu(i)
            if j not in relabel:
                test.append(j)
                relabel[j] = n
                n += 1
            uu[relabel[i]] = relabel[j]

        return Origami_dense_pyx(tuple(rr), tuple(uu))

    def is_primitive(self, return_base=False):
        r"""
        An origami is primitive if it does not cover an other origami.

        An origami is primitive if the action of the monodromy group has no non
        trivial block.

        EXAMPLE::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_primitive()
            True
            sage: o = Origami('(1,2)(3,4)', '(1,3,5,6)(2,4)')
            sage: o.is_primitive()
            False
        """
        from sage.arith.all import is_prime
        if is_prime(self.nb_squares()):
            return True

        return libgap.IsPrimitive(self.monodromy()).sage()

    def is_quasi_primitive(self):
        r"""
        An origami is quasi primitive if it is reduced and all intermediate
        covers are of genus 1.

        SEE ALSO:

            :meth:`is_primitive` and :meth:`is_reduced`

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)(3,4)', '(1,3)')
            sage: o.is_primitive()
            False
            sage: o.is_quasi_primitive()
            True

            sage: o = Origami('(1,2,3,4)(5)(6)', '(1,5)(3,6)')
            sage: o.is_primitive()
            False
            sage: o.is_quasi_primitive()
            False
        """
        from sage.arith.all import is_prime
        if is_prime(self._n):
            return True

        #TODO: this is *very* stupid
        # there should be something more direct in gap
        return self.is_reduced() and all(o == self or o.genus() == 1 for o in self.lattice_of_quotients())

    def intermediate_covers(self, degree=None):
        r"""
        Return the list of intermediate covers of this origami.

        If ``degree`` is specified, only intermediate covers of given degree are
        returned.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(3,4,5)', '(1,2,3)(4,6,7)(5,8,9)')
            sage: for oo in o.intermediate_covers():
            ....:    print(oo.nb_squares())
            ....:    print(oo)
            ....:    print("- - - - - -")
            1
            (1)
            (1)
            - - - - - -
            3
            (1)(2)(3)
            (1,2,3)
            - - - - - -
            9
            (1)(2)(3,4,5)(6)(7)(8)(9)
            (1,2,3)(4,6,7)(5,8,9)
            - - - - - -
            sage: o.intermediate_covers(degree=3)
            [(1)(2)(3)
            (1,2,3)]
        """
        G = self.monodromy()
        n = self.nb_squares()
        r = self.r()
        u = self.u()
        blocks = map(list, libgap(G).AllBlocks())
        if degree is not None:
            degree = int(degree)
            n_div_d = n // degree
            if n % degree:
                raise ValueError("degree={} does not divide the number of squares n={}".format(degree, n))
            blocks = [b for b in blocks if len(b) == n_div_d]

        covers = []
        for b in blocks:
            orbit = libgap.Orbit(G, b, libgap.OnSets)
            action = libgap.Action(G, orbit, libgap.OnSets)
            rr, uu = libgap.GeneratorsOfGroup(action)
            covers.append(origami_from_gap_permutations(rr, uu))
        if degree is None or degree == 1:
            covers.append(Origami_dense_pyx((0,), (0,)))
        if degree is None or degree == self._n:
            covers.append(self)
        if degree is None:
            covers.sort(key=lambda o: o.nb_squares())
        return covers

    def lattice_of_quotients(self, verbose=False):
        r"""
        Return the lattice of quotients of this origami.

        The set of quotients of an origami contain a maximal element (itself)
        and a minimal element (the 1-torus). More generally, it is organised as
        a lattice.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: P = o.normal_cover().lattice_of_quotients(); P
            Finite lattice containing 6 elements
            sage: for p in P:
            ....:     print("%d %s" % (p.nb_squares(), p.stratum_component()))
            6 H_3(2^2)^odd
            3 H_2(2)^hyp
            3 H_2(2)^hyp
            3 H_2(2)^hyp
            2 H_1(0)^hyp
            1 H_1(0)^hyp

            sage: o = Origami('(1,2)(3,4)', '(1,3)')
            sage: o.lattice_of_quotients()
            Finite lattice containing 3 elements
        """
        from sage.combinat.posets.lattices import LatticePoset
        G = libgap(self.monodromy())
        n = self.nb_squares()
        r = self.r()
        u = self.u()
        blocks = list(G.AllBlocks())
        if verbose:
            print(blocks)
        d = {}
        for b in blocks:
            orbit = G.Orbit(b, libgap.OnSets)
            action = G.Action(orbit, libgap.OnSets)
            rr, uu = libgap.GeneratorsOfGroup(action)
            d[frozenset(map(Integer, b))] = origami_from_gap_permutations(rr, uu)
        d[frozenset(range(1, n+1))] = Origami_dense_pyx((0,), (0,))
        d[frozenset([1])] = self
        if verbose:
            for i in d:
                print(i, "->\n", d[i])
        E = d.values()
        R = [(d[i], d[j]) for i in d for j in d if i.issubset(j)]
        return LatticePoset((E, R))

    def is_regular(self):
        r"""
        An origami is regular if its automorphism group acts transitively on the
        squares.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis

            sage: o = origamis.EierlegendeWollmilchsau()
            sage: o.is_regular()
            True
            sage: o.is_normal()
            True

        ::

            sage: o = Origami('(1,3,2,4,5,6)', '(1,5)')
            sage: o.is_regular()
            False
            sage: o.is_normal()
            False
        """
        return libgap.IsTransitive(
                        self.automorphism_group(),
                        libgap.eval("[1..%d]" % (self.nb_squares()))
                        ).sage()

    def is_quasi_regular(self):
        r"""
        An origami `(r, u)` is *quasi-regular* if the normal closure of the
        commutator `c = rur^{-1}u^{-1}` is contained in the automorphism group.

        Equivalently, a quasi-regular origami is a translation surface which is
        a normal cover of a torus ramified over several rational points.
        """
        return self.quotient().genus() == 1
        # other method from Gap
        #G = self.monodromy()
        #A = self.automorphism_group()
        #r = self.r(); u = self.u()
        #C = G.subgroup([r*u*~r*~u])
        #NC = gap.NormalClosure(G, C)
        #return gap.IsSubgroup(A, NC)

    def is_normal(self):
        r"""
        Tests if this origami is a normal cover of the torus

        An origami is normal if the subgroup of `F_2` that defines the cover is
        normal. It is equivalent to say that the order of the automorphism group
        equals the number of squares or that the automorphism group acts
        transitively on the squares.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.is_normal()
            False
            sage: o.is_regular()
            False

            sage: o = origamis.Escalator(4)
            sage: o.is_normal()
            True
            sage: o.is_normal() == o.is_regular()
            True
        """
        return self.automorphism_group().order() == self.nb_squares()

    def cover(self, sr, su, check=True, as_tuple=False):
        r"""
        Build the (ramified) cover of this origami by sr and su.

        INPUT:

        - `sr`, `su` - two list of N permutations where N is the number of
          squares of this origami

        - `check` - whether or not check the input

        - `as_tuple` - assume that sr and su are list of tuples of the same
          length and corresponds to permutations of [0, ..., d-1] (much more
          efficient in time)

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)'); o
            (1,2)(3)
            (1,3)(2)
            sage: o.cover(['(1,2)', '', ''], ['', '', ''])
            (1,5,4,2)(3)(6)
            (1,3)(2)(4,6)(5)
        """
        from sage.groups.perm_gps.permgroup_named import SymmetricGroup

        cdef int N = self._n
        cdef size_t i, j

        if len(sr) != N or len(su) != N:
            raise ValueError("sr and su should be two lists of length %d" % N)

        if not as_tuple:
            sr = [PermutationConstructor(x, check=check) for x in sr]
            su = [PermutationConstructor(x, check=check) for x in su]
            sr = [[i-1 for i in x.domain()] for x in sr]
            su = [[i-1 for i in x.domain()] for x in su]

            #the more direct
            #    sr = [[i-1 for i in PermutationConstructor(x, check=check).domain()] for x in sr]
            #    su = [[i-1 for i in PermutationConstructor(x, check=check).domain()] for x in su]
            #does not work

            d = max(max(len(x) for x in sr), max(len(x) for x in su))

            for p in sr: p.extend(range(len(p), d))
            for p in su: p.extend(range(len(p), d))
        else:
            d = len(sr[0])

        cdef list rr = [None]*N*d
        cdef list uu = [None]*N*d

        for i in range(N):
            for j in range(d):
                rr[i+N*j] = self._r[i] + N*sr[i][j]
                uu[i+N*j] = self._u[i] + N*su[i][j]

        return Origami_dense_pyx(tuple(rr), tuple(uu))

    def normal_cover(self):
        r"""
        Return the normal cover of this origami.
        """
        G = self.monodromy()
        A = libgap.Action(G, G, libgap.OnRight)
        r, u = libgap.GeneratorsOfGroup(A)
        return origami_from_gap_permutations(r, u)

    def rename(self, name):
        self._name = name


    #
    # Automorphisms and quotients
    #

    def monodromy(self, relative=False):
        r"""
        Return the monodromy group of the origami.

        The monodromy group of an origami is the group generated by the
        permutations ``r`` and ``u`` from which it is defined.

        INPUT:

        - ``relative`` -- if ``True`` return the monodromy relative to the
          largest torus over which this origami is a cover (possibly ramified
          over several points)

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: G = o.monodromy()
            sage: G
            Permutation Group with generators [(1,2), (1,3)]
            sage: G.order()
            6
        """
        from sage.groups.perm_gps.permgroup import PermutationGroup
        if relative is False or self.lattice_of_absolute_periods() == (1, 0, 1):
            return PermutationGroup([self.r(), self.u()], canonicalize=False)

        elif relative is True:
            d = self.optimal_degree()

            G = self.monodromy()
            B = map(list, libgap(G).AllBlocks())
            B = [b for b in B if len(b) == d]
            if len(B) != 1:
                for b in B:
                    orbit = libgap.Orbit(G, b, libgap.OnSets)
                    action = libgap.Action(G, orbit, libgap.OnSets)
                    if libgap.IsAbelian(action):
                        break
                else:
                    raise RuntimeError("an error occurred... please contact 20100.delecroix@gmail.com")
            else:
                b = B[0]
            H = libgap.Stabilizer(G, b, libgap.OnSets)
            action = libgap.Action(H, b, libgap.OnPoints)
            return PermutationGroup(list(libgap.GeneratorsOfGroup(action)), canonicalize=False)
        else:
            raise ValueError("relative must be a boolean")

    def automorphism_group(self):
        r"""
        Return the automorphism group of the origami as a permutation group.

        The automorpism group of a translation surface is the set of affine
        diffeomorphisms which have a trivial linear part. For an origami, it
        corresponds combinatorially to the centralizer of the group generated by
        the permutations ``r`` and ``u`` that define this origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

        The L with 3 squares has no automorphisms::

            sage: o = Origami('(1,2)', '(1,3)')
            sage: G = o.automorphism_group()
            sage: G.order()
            1

        This 4-square square-tiled surface in H(1, 1) has non trivial
        automorphism for which the quotient is a torus with two squares::

            sage: o = Origami('(1,2)(3,4)', '(2,3)')
            sage: G = o.automorphism_group()
            sage: G.order()
            2
            sage: oo = o.quotient(G)
            sage: oo
            (1,2)
            (1)(2)
        """
        from sage.all import SymmetricGroup
        Sn = SymmetricGroup(self.nb_squares())
        G = libgap.Subgroup(Sn, [self.r(), self.u()])
        C = libgap.Centralizer(Sn, G)
        return Sn.subgroup(list(libgap.GeneratorsOfGroup(C)))

    translation_group = automorphism_group

    def quotient(self, H=None):
        r"""
        Return a quotient of self by the group ``H``.

        The group ``H`` must be a subgroup of the automorphism group of this
        origami. If ``H`` is None, it is set by default to the full automorphism
        group.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)(3,4)', '(2,3)')
            sage: G = o.automorphism_group()
            sage: G.order()
            2
            sage: oo = o.quotient(G)
            sage: print(oo)
            (1,2)
            (1)(2)
            sage: oo.genus()
            1
        """
        if H is None:
            H = self.translation_group()
        elif not H.is_subgroup(self.automorphism_group()):
            raise ValueError("H must be a sub group of the automorphism group")

        classes = H.orbits()
        classes_inv = [None] * self._n
        cdef size_t i, j
        for i, c in enumerate(classes):
            for j in c:
                classes_inv[j-1] = i

        cdef list rr = []
        cdef list uu = []

        for c in classes:
            rr.append(classes_inv[self._r[c[0]-1]])
            uu.append(classes_inv[self._u[c[0]-1]])

        return Origami_dense_pyx(tuple(rr), tuple(uu))

    #
    # String representation, latex and plot
    #

    def __str__(self):
        r"""
        String representation

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: print(Origami('(1,2)', '(1,3)').__str__())
            (1,2)(3)
            (1,3)(2)
            sage: print(Origami('(1,2)', '(1,3)', name='toto').__str__())
            (1,2)(3)
            (1,3)(2)
        """
        sr = self.r().cycle_string(singletons=True)
        su = self.u().cycle_string(singletons=True)
        return sr + "\n" + su


    def __repr__(self):
        if self._name is not None:
            return self._name
        return self.__str__()

    def _latex_(self):
        r"""
        Latex representation of self

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: print(Origami('(1,2)', '(1,3)')._latex_())
            (1,2)(3) \atop (1,3)(2)
        """
        sr = self.r().cycle_string(singletons=True)
        su = self.u().cycle_string(singletons=True)
        return sr + " \\atop " + su

    def set_positions(self, pos=None):
        r"""
        Set positions of the squares for plotting.

        INPUT:

        - ``pos`` - list of pairs of coordinates, one for each square
        """
        if pos is None:
            if self._pos is not None:
                return

            # use cylinder diagram to organize the squares into cylinders
            from surface_dynamics.misc.permutation import perm_invert
            o = self.__copy__()
            rel = o._set_standard_form(return_map=True)
            rel = perm_invert(rel)
            cyls = o.cylinder_decomposition()
            H = 0
            pos = [None] * self.nb_squares()
            k = 0
            for cyl in cyls:
                _, _, w, h, _, _ = cyl
                for j in range(h):
                    for i in range(w):
                        pos[rel[k]] = (i, H)
                        k += 1
                    H += 1
                H += 0.5

        r = self.r_tuple()
        u = self.u_tuple()
        if len(pos) != self.nb_squares():
            raise ValueError("not enough positions")

        rl_frontiers = []
        tb_frontiers = []
        for i in range(self.nb_squares()):
            x, y = pos[i]
            xx, yy = pos[r[i]]
            if y != yy or x+1 != xx:
                rl_frontiers.append(i)

            xx, yy = pos[u[i]]
            if x != xx or y+1 != yy:
                tb_frontiers.append(i)

        self._pos = pos
        self._rl_frontiers = set(rl_frontiers)
        self._tb_frontiers = set(tb_frontiers)

    @options(
            side=True, side_color=(0.6, 0.6, 0.6), side_linestyle='dashed', side_alpha=0.8,
            frontier=True, frontier_color=(0.4, 0.4, 0.4),
            square=True, square_color=(0, 0, 1), square_alpha=0.1,
            text_square=True, text_square_color=(0, 0, 0), text_square_fontsize=11,
            text_frontier=True, text_frontier_color=(0.4, 0.4, 0.4), text_frontier_fontsize=8,
            vertex=True)
    def plot(self, **args):
        r"""
        Plot self.

        The positions of each square follow a naive algorithm. If you belive
        that a better picture exists look at the method .set_positions()

        EXAMPLES::

            sage: from surface_dynamics.all import origamis
            sage: o = origamis.Escalator(3)
            sage: o.plot()  # not tested (problem with matplotlib font caches)
            Graphics object consisting of 71 graphics primitives
        """
        from sage.plot.plot import Graphics
        from sage.plot.polygon import polygon2d
        from sage.plot.line import line2d
        from sage.plot.text import text
        from sage.plot.colors import rainbow
        from sage.plot.point import point2d

        # in case self._pos is undefined
        self.set_positions()

        d = {
            'side': {},
            'frontier':{},
            'square':{},
            'text_square':{},
            'text_frontier':{}
            }
        for key, value in iteritems(args):
            for k in d.keys():
                if key.startswith(k + '_'):
                    d[k][key[len(k)+1:]] = value

        r = self.r_tuple()
        u = self.u_tuple()

        urvertices = {}
        dlvertices = {}
        vv = self.vertices()
        colors = rainbow(len(vv), 'rgbtuple')
        for j in range(len(vv)):
            v = vv[j]
            for i in v.up_right_tuple():
                urvertices[i-1] = colors[j]
            for i in v.down_left_tuple():
                dlvertices[i-1] = colors[j]

        G = Graphics()
        for i in range(self.nb_squares()):
            x0, y0 = self._pos[i]
            x1 = x0+1; y1 = y0+1
            if args['square']:
                G += polygon2d([(x0, y0), (x1, y0), (x1, y1), (x0, y1)], **d['square'])
            if args['text_square']:
                G += text("%d" % (i+1), (x0+0.5, y0+0.5), **d['text_square'])

        for i in range(self.nb_squares()):
            x0, y0 = self._pos[i]
            x1 = x0+1; y1 = y0+1

            if i in self._rl_frontiers:
                xx0, yy0 = self._pos[r[i]]
                if args['frontier']:
                    G += line2d([(x1, y0), (x1, y1)], **d['frontier'])
                    G += line2d([(xx0, yy0), (xx0, yy0+1)], **d['frontier'])
                if args['text_frontier']:
                    G += text("%d" % (r[i]+1), (x1-0.08, y0+0.5), **d['text_frontier'])
                    G += text("%d" % (i+1), (xx0+0.08, yy0+0.5), **d['text_frontier'])
                if args['vertex'] and i in urvertices:
                    G += point2d((xx0, yy0+1), color=urvertices[i], pointsize=30)
                if args['vertex'] and r[i] in dlvertices:
                    G += point2d((x0+1, y0), color=dlvertices[r[i]], pointsize=30)
            else:
                G += line2d([(x1, y0), (x1, y1)], **d['side'])

            if i in self._tb_frontiers:
                xx0, yy0 = self._pos[u[i]]
                if args['frontier']:
                    G += line2d([(x0, y1), (x1, y1)], **d['frontier'])
                    G += line2d([(xx0, yy0), (xx0+1, yy0)], **d['frontier'])
                if args['text_frontier']:
                    G += text("%d" % (u[i]+1), (x0+0.5, y1-0.08), **d['text_frontier'])
                    G += text("%d" % (i+1), (xx0+0.5, yy0+0.08), **d['text_frontier'])
                if args['vertex'] and i in urvertices:
                    G += point2d((xx0+1, yy0), color=urvertices[i], pointsize=30)
                if args['vertex'] and u[i] in dlvertices:
                    G += point2d((x0, y0+1), color=dlvertices[u[i]], pointsize=30)
            else:
                if args['frontier']:
                    G += line2d([(x0, y1), (x1, y1)], **d['side'])

            if args['vertex'] and i in dlvertices:
                G += point2d((x0, y0), color=dlvertices[i], pointsize=30)
            if args['vertex'] and i in urvertices:
                G += point2d((x1, y1), color=urvertices[i], pointsize=30)

        G.axes(False)
        return G

    def show(self):
        r"""
        Show a picture of this origami.
        """
        self.plot().show(axes=False, aspect_ratio=1)

    #
    # Cylinder decomposition in horizontal direction
    #

    def cylinder_decomposition(self):
        r"""
        Return the cylinder decomposition of the standard form of this origami.

        OUTPUT:

        A list of cylinders where each cylinder is a 6-tuple
        ``(bot, top, w, h, bot_twist, top_twist)`` where

        - ``bot`` and ``top`` are list of right squares adjacent to
          singularities (the order is in the direction of the permutation ``r`` of
          the origami)

        - ``w`` and ``h`` are width and height of the cylinder

        - ``bot_twist`` and ``top_twist`` are twist between the minimum square
          number and the minimum square number adjacent to a singularity.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: r  = '(1,2,3,4)(5,6)'
            sage: u0 = '(1,5)(2,6)(3)(4)'
            sage: u1 = '(1,5,2,6,3,4)'

            sage: Origami(r, u0).cylinder_decomposition()
            [([(1, 3)], [(3, 1)], 2, 1, 0, 0),
             ([(3, 1), (5, 5)], [(5, 5), (1, 3)], 4, 1, 0, 0)]
            sage: Origami(r, u1).cylinder_decomposition()
            [([(1, 6)], [(3, 1)], 2, 1, 0, 0),
             ([(3, 1), (5, 4)], [(1, 6), (5, 4)], 4, 1, 0, 1)]
        """
        o = self.to_standard_form()
        r = o.r()
        ri = ~r
        u = o.u()
        ui = ~u

        udr_sq = [] # (up-right, down-right) pairs adjacent to singularities
        for v in (ui*ri*u*r).cycle_tuples():
            udr_sq.extend((j, ui(j)) for j in v)

        if not udr_sq: # torus
            udr_sq = [(1, ui(1))]

        udr_sq.sort()
        udr_tot = set(udr_sq)

        cyls = []
        while udr_sq:
            j, ji = udr_sq.pop(0)
            k = r.orbit(j)  # the min is not necessarily at a singularity
            jj = min(k)     # because of the standard form. Hence we
                            # construct the r orbit of j.
                            # the bot twist is the distance between the
                            # min square and the min singularity
                            # width is r^-1(j)-j
            bot_twist = j-jj
            w = ri(jj)-jj+1
            bot = [(j, ji)]
            while udr_sq and udr_sq[0][0] < w+jj:
                bot.append(udr_sq.pop(0))
            l = min(x[1] for x in udr_tot)

            h = (l-jj)//w + 1
            top = list(filter(lambda i: jj+(h-1)*w <= i[1] and i[1] < jj+h*w, udr_tot))
            top.sort(key=lambda x: -x[1])

            top_twist = min(t[1] for t in top) - (jj+(h-1)*w)

            #top.reverse()
            cyls.append((bot, top, w, h, bot_twist, top_twist))

            udr_tot = udr_tot.difference(top)

        return cyls

    def num_cylinders(self):
        r"""
        Return the number of cylinders of this origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis

            sage: Origami('(1,2)', '(1,3)').num_cylinders()
            2
            sage: origamis.Stair(5).num_cylinders()
            3
            sage: Origami('(1,2)', '(1)(2)').num_cylinders()
            1
        """
        return len(self.cylinder_decomposition())

    def cylinder_diagram(self, data=False):
        r"""
        Return the cylinder diagram corresponding to the horizontal direction.

        If ``data`` is ``True``, also return the list of lengths of
        separatrices, the heights of cylinders and the twists.

        The *cylinder diagram* of a completely periodic surface encodes the
        combinatorics of cylinders and saddle connections. From a cylinder
        diagram and its metric data, it is possible to build the surface back.

        INPUT:

        - ``data`` - boolean (default: False) - if True, return the cylinder
          diagrams, the lengths, the heights and twists.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

        The two examples in the stratum H(2)::

            sage: o1 = Origami('(1,2,3)', '(2,3)')
            sage: o1.stratum()
            H_2(2)
            sage: c1 = o1.cylinder_diagram()
            sage: c1.ncyls()
            1
            sage: c1.nseps()
            3
            sage: o2 = Origami('(1,2)', '(1,3)')
            sage: o2.stratum()
            H_2(2)
            sage: c2 = o2.cylinder_diagram()
            sage: c2.ncyls()
            2
            sage: c2.nseps()
            3

            sage: r = (1, 2, 0, 4, 5, 6, 3)
            sage: u = (1, 2, 3, 5, 4, 0, 6)
            sage: o = Origami(r, u, as_tuple=True)
            sage: c, lengths, heights, twists = o.cylinder_diagram(True)
            sage: c.cylcoord_to_origami(lengths, heights, twists) == o
            True

        TESTS:

        Check consistency of cylinder data::

            sage: r = '(1,2,3,4,5,6,7,8)(9,10,11,12,13,14,15,16)'
            sage: u = '(1,9,3,15,5,13,7,11)(2,14,8,16,6,10,4,12)'
            sage: o3 = Origami(r, u)
            sage: c3 = o3.cylinder_diagram()
            sage: c3.ncyls()
            2
            sage: c3.nseps()
            8
            sage: r = '(1,2,3,4)(5,6,7,8)(9,10,11,12)(13,14,15,16)'
            sage: u = '(1,5,3,10)(2,9,13,11)(4,8,15,6)(7,14,12,16)'
            sage: o4 = Origami(r, u)
            sage: c4 = o4.cylinder_diagram()
            sage: c4.ncyls()
            4
            sage: c4.nseps()
            8
        """
        from surface_dynamics.flat_surfaces.separatrix_diagram import CylinderDiagram

        cyl_dec = self.cylinder_decomposition()

        # find a renumbering for edges
        edges = []
        cyl_ren = []
        for i, cyl in enumerate(cyl_dec):
            edges.extend(cyl[0])
            cyl_ren.append((cyl[0][0][0], i))

        edges.sort()   # index to edge
        e2i = dict((edges[i], i) for i in range(len(edges)))  # edge to index
        cyl_ren.sort()
        cyl_ren = dict((j[0], i) for i, j in enumerate(cyl_ren))

        # now build a cylinder diagram with lengths, twists and heights
        cyls = []
        lengths = [None]*len(edges)
        heights = [None]*len(cyl_dec)
        twists = []
        for b, t, w, h, _, _ in cyl_dec:
            ww = w
            n = cyl_ren[b[0][0]]
            for i in range(len(b)-1):
                lengths[e2i[b[i]]] = b[i+1][0] - b[i][0]
                ww -= lengths[e2i[b[i]]]
            lengths[e2i[b[-1]]] = ww
            heights[n] = h

            bot = tuple(e2i[e] for e in b)
            top = tuple(e2i[e] for e in t)
            s_bot = edges[min(bot)][0]
#            print "min(bot) = %d   s_bot = %d" % (min(bot), s_bot)
            s_top = edges[min(top)][1]
#            print "min(top) = %d   s_top = %d" % (min(top), s_top)
            twists.append((s_top - s_bot) % w)
            cyls.append((bot, top))

        if data:
            return CylinderDiagram(cyls), lengths, heights, twists
        else:
            return CylinderDiagram(cyls)

    #
    # Homology
    #

    def absolute_period_generators(self):
        r"""
        Return a generating set of the absolute periods of this origami.

        To each curve on an origami, we can associate its holonomy (that is an
        element of `\ZZ \times \ZZ`). This function returns a generating set of
        the module generated by holonomies of closed curves.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2,3,4)(5,6)', '(1,5)(2,6)')
            sage: o.absolute_period_generators()
            [(2, 0), (2, 0), (0, 1), (0, 1)]
        """
        cyl, lengths, heights, twists = self.cylinder_diagram(data=True)
        r = cyl.to_ribbon_graph_with_holonomies(lengths, heights, twists)
        periods = []
        for c in r.cycle_basis():
            s = sum(r._holonomies[e[0]] for e in c)
            if s:
                periods.append(s)
        return periods

    def stratum(self, fake_zeros=False):
        r"""
        Stratum of this origami.

        INPUT:

        - ``fake_zeros`` -- boolean (default ``False``) whether the stratum should
          include zeros of order 0 corresponding to the regular corners of the
          origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.stratum()
            H_2(2)

            sage: r = '(1,2,3,4,5,6,7,8,9,10,11,12)'
            sage: u = '(1,2,3,4,12)(5,7)(6)(8,10)(9)(11)'
            sage: o = Origami(r, u)
            sage: o.stratum()
            H_4(3, 2, 1)
            sage: o.stratum(True)
            H_4(3, 2, 1, 0^3)
        """
        from surface_dynamics.flat_surfaces.abelian_strata import AbelianStratum
        degrees = self.vertex_degrees(fake_zeros)
        if degrees:
            return AbelianStratum(degrees)
        return AbelianStratum(0)

    def genus(self):
        r"""
        Return the genus of the origami

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.genus()
            2
            sage: o = Origami('(1,2)(3,4)', '(1,3)')
            sage: o.genus()
            2
        """
        return self.stratum().genus()

    def veech_group(self):
        r"""
        Return the Veech group of this origami.

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: G = o.veech_group()
            sage: G
            Arithmetic subgroup with permutations of right cosets
             S2=(2,3)
             S3=(1,2,3)
             L=(1,2)
             R=(1,3)

        Most geometric information on the quotient of the upper half plane by
        the Veech group can be recovered from ``G``::

            sage: G.index()
            3
            sage: G.genus()
            0
            sage: G.ncusps()
            2
            sage: G.nu2()
            1
            sage: G.nu3()
            0

        As well as some arithmetic informations::

            sage: G.is_congruence()
            True
            sage: G.generalised_level()
            2

        Note that the fact of being congruent is rather exceptional::

            sage: o = Origami('(1,2,3,4)', '(4,5)')
            sage: o.veech_group().is_congruence()
            False

            sage: o = Origami('(1,2,3,4,5)', '(5,6)')
            sage: o.veech_group().is_congruence()
            False

            sage: o = Origami('(1,2,3,4,5,6)', '(6,7)')
            sage: o.veech_group().is_congruence()
            False

        TESTS:

        An error is raised if the origami is not connected::

            sage: o = Origami('(1,2)', '(3,4)')
            Warning: the origami is not connected
            sage: o.veech_group()
            Traceback (most recent call last):
            ...
            ValueError: the origami is not connected! The Veech group
            computation is disabled in that case.
        """
        return self.teichmueller_curve().veech_group()

    @cached_method
    def teichmueller_curve(self):
        r"""
        Return the teichmueller curve of this origami

        The result is cached for future usage.

        .. SEEALSO::

            :class:`~surface_dynamics.flat_surfaces.origamis.teichmueller_curve.TeichmuellerCurveOfOrigami_class`

        EXAMPLES::

            sage: from surface_dynamics.all import Origami
            sage: o = Origami('(1,2)', '(1,3)')
            sage: t = o.teichmueller_curve()
            sage: t
            Teichmueller curve of the origami
            (1)(2,3)
            (1,2)(3)
            sage: t.sum_of_lyapunov_exponents()
            4/3
            sage: for o in t.cusp_representatives():
            ....:     print(o[0])
            ....:     print(o[1])
            (1)(2,3)
            (1,2)(3)
            2
            (1,2,3)
            (1)(2,3)
            1
        """
        if not self.is_connected():
            raise ValueError("the origami is not connected! The Veech group computation is disabled in that case.")
        from .teichmueller_curve import TeichmuellerCurveOfOrigami
        return TeichmuellerCurveOfOrigami(self)

    def sum_of_lyapunov_exponents(self):
        r"""
        Return the sum of Lyapunov exponents for this origami

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.sum_of_lyapunov_exponents()
            4/3
            sage: o = Origami('(1,2)(3,4)', '(2,3)')
            sage: o.sum_of_lyapunov_exponents()
            3/2

        TESTS:

        An error is raised if the origami is not connected::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(3,4)')
            Warning: the origami is not connected
            sage: o.sum_of_lyapunov_exponents()
            Traceback (most recent call last):
            ...
            ValueError: the origami is not connected! The Veech group
            computation is disabled in that case.
        """
        return self.teichmueller_curve().sum_of_lyapunov_exponents()

    @cached_method
    def _vertices(self, register_automorphism_action=False):
        r"""
        Return an object that models the set of vertices
        """
        from surface_dynamics.flat_surfaces.origamis.origami import OrigamiVertices
        return OrigamiVertices(self, register_automorphism_action)

    def vertices(self, register_automorphism_action=False):
        r"""
        INPUT:

        - ``register_automorphism_action`` - (default is ``False``) whether the
          action of the automorphism group of the origami is registered on the
          vertices

        EXAMPLES::

            sage: from surface_dynamics.all import origamis
            sage: o = origamis.Escalator(4)
            sage: o.vertices()
            [vertex (1, 5), vertex (2, 6), vertex (3, 7), vertex (4, 8)]
        """
        return [v for v in self._vertices() if v.degree()]

    def vertex_degrees(self, bint fake_zeros=False):
        r"""
        Return the list of degree of the vertices

        INPUT:

        - ``fake_zeros`` -- boolean (default ``False``) whether we return also the
          2 pi angle degrees

        EXAMPLES::

            sage: from surface_dynamics.all import Origami, origamis

            sage: o = Origami('(1,2,3,4,5)', '(1,5,3,2,4)')
            sage: o.vertex_degrees()
            [4]

            sage: o = origamis.ProjectiveLine(5)
            sage: o.vertex_degrees()
            [2, 2]

            sage: r = '(1,2,3,4,5,6,7,8,9,10,11,12)'
            sage: u = '(1,2,3,4,12)(5,7)(6)(8,10)(9)(11)'
            sage: o = Origami(r, u)
            sage: o.vertex_degrees()
            [3, 2, 1]
            sage: o.vertex_degrees(fake_zeros=True)
            [3, 2, 1, 0, 0, 0]
        """
        cdef int * c = <int *> malloc(self._n * sizeof(int))
        cdef int i, d
        cdef list res = []
        for i in range(self._n):
            c[self._r[self._u[i]]] = self._u[self._r[i]]
        for i in range(self._n):
            if c[i] >= 0:
                d = 0
                while c[i] >= 0:
                    d += 1
                    j = c[i]
                    c[i] = -1
                    i = j
                if fake_zeros or d > 1:
                    res.append(d - 1)
        free(c)
        res.sort(reverse=True)
        return res

    def nb_vertices(self):
        r"""
        Return the number of singularities of this origami

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: o.nb_vertices()
            1
            sage: o = Origami('(1,2,3)(4,5,6)', '(3,4)')
            sage: o.nb_vertices()
            2
        """
        return len(self.vertices())

cdef gl2z_orbits(origamis, int n, int limit):
    r"""
    Compute the action of `GL(2, \ZZ)` on the set ``origamis`` whose elements
    should be in normal form and have same number of squares ``n``. An optional
    argument ``limit`` can be used in order to stop the computation if too much
    origami are found (and then ``None`` is returned). If limit is a non
    positive number then the function does not take care of it.

    The generators of `GL(2, ZZ)` considered are

    I =
    0 1
    1 0

    L =
    1 1
    0 1
    """
    cdef list orbits = []
    cdef dict l_edges
    cdef dict i_edges
    cdef int i
    cdef size_t N = 2*n*sizeof(int)
    cdef set waiting = set([])
    cdef int * renum = <int *> malloc(n * sizeof(int))
    cdef int *r = <int *> malloc(N)
    cdef int *u = r+n
    cdef int *rr = NULL
    cdef int *uu = NULL
    cdef int *rrr = NULL
    cdef int *uuu = NULL
    cdef Origami_dense_pyx o, oo, ooo
    cdef bool VERBOSE = False

    while origamis:
        o = origamis.pop()
        if o._l_edges:
            o._l_edges.clear()
            o._i_edges.clear()
        rr = <int *> malloc(N)
        uu = rr+n
        if VERBOSE:
            print("pop origami\n r=%s\n u=%s" % (str(o.r()), str(o.u())))
            print("check:")
            print("  l = %s at %d" % (o._l_edges, id(o._l_edges)))
            print("  i = %s at %d" % (o._i_edges, id(o._i_edges)))
        l_edges = o._l_edges
        i_edges = o._i_edges

        for i from 0 <= i < n:
            r[i] = o._r[i]
            u[i] = o._u[i]
        waiting.add(o)

        # at each step o, r, u is set to the current origami
        # rr, uu is memory allocated
        # r, u   pointed by o
        while True:
            if VERBOSE:
                print("start new cusp...")
            if o in l_edges:
                raise ValueError("%s seen before" % str(o))

            # we compute backward the l-orbit of o
            for i from 0 <= i < n:
                rr[i] = r[i]
                uu[i] = r[u[i]]
            origami_normal_form(rr, uu, renum, n)

            ooo = o
            while origami_diff(r, rr, n):
                oo = o._new_c(rr)
                if VERBOSE:
                    print(" new elt in cusp")
                    print(" r = %s" % oo.r())
                    print(" u = %s" % oo.u())
                    print(" go")
                if oo in origamis:
                    if VERBOSE:
                        print(" remove origami in the set")
                    origamis.remove(oo)

                waiting.add(oo)
                l_edges[oo] = ooo
                ooo = oo
                rrr = rr
                uuu = uu
                rr = <int *>malloc(N)
                uu = rr+n
                for i from 0 <= i < n:
                    rr[i] = rrr[i]
                    uu[i] = rrr[uuu[i]]
                origami_normal_form(rr, uu, renum, n)

            # at this point rr is memory allocated

            l_edges[o] = ooo
            if limit > 0 and len(l_edges) > limit: # test the size
                if VERBOSE:
                    print("oversize")
                l_edges.clear()
                i_edges.clear()
                waiting.clear()
                r = rr; u = uu
                break

            # then we create i-edges until we find a new guy
            # we set r, u to be available
            if VERBOSE: print("end of cusp, apply symmetry")
            r = rr; u = uu
            while waiting:
                if VERBOSE:
                    print(" new try...")
                oo = waiting.pop()
                rr = oo._r; uu = oo._u
                for i from 0 <= i < n:
                    r[i] = uu[i]
                    u[i] = rr[i]
                origami_normal_form(r, u, renum, n)
                if origami_diff(r, rr, n): # not symmetric under r <-> u
                    o = o._new_c(r)
                    rr = <int *>malloc(N)
                    uu = rr+n
                    i_edges[o] = oo
                    i_edges[oo] = o
                    if o in waiting: # we find a fake new guy
                        if VERBOSE: print(" ...was already there")
                        waiting.remove(o)
                        r = rr; u = uu
                    else: # we find a real new guy
                        if VERBOSE:
                            print("go elsewhere")
                        if o in origamis:
                            origamis.remove(o)
                        break
                else: # symmetric under r <-> u
                    if VERBOSE:
                        print(" ...symmetric guy")
                    i_edges[oo] = oo
                    rr = NULL
                    uu = NULL
            else:
                break

        if l_edges: # append if we do not quit because of oversize
            orbits.append((l_edges, i_edges))
            if VERBOSE:
                    print("new orbit of size %d" % (len(l_edges)))
                    print("check: waiting=", waiting)

    free(renum)
    free(rr)

    return orbits

cpdef sl2z_orbits(origamis, int n, int limit):
    r"""
    Action of the matrices l, r, s

    EXAMPLES::

        sage: from surface_dynamics.all import AbelianStratum
        sage: from surface_dynamics.flat_surfaces.origamis.origami_dense import sl2z_orbits
        sage: C = AbelianStratum(2,2).odd_component()
        sage: origamis = C.origamis(10)
        sage: len(origamis)
        8955
        sage: slorbits = sl2z_orbits(origamis, 10, 0)
        sage: len(slorbits)
        19
        sage: sum(len(o[0]) for o in slorbits)
        8955
        sage: for n in range(6, 10):
        ....:     print(len(C.origamis(n)))
        69
        270
        1260
        3384
    """
    slorbits = []
    glorbits = gl2z_orbits(origamis, n, limit)

    for L, I in glorbits:
        o = next(iterkeys(L))
        l, r, s = sl_orbit_from_gl_orbit(o, L, I)
        slorbits.append((l, r, s))
        if len(l) != len(L):
            l, r, s = sl_orbit_from_gl_orbit(o.mirror(). relabel(), L, I)
            slorbits.append((l, r, s))

    return slorbits


cdef class PillowcaseCover_dense_pyx:
    def __cinit__(self):
        r"""
        TESTS::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: loads(dumps(o)) == o
            True
        """
        self._n = 0
        self._g = NULL
        self._l_edges = None
        self._i_edges = None

    def __init__(self, tuple g0, tuple g1, tuple g2, tuple g3):
        r"""
        TESTS::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami([2, 1, 3], [1, 3, 2])
            sage: o == loads(dumps(o))
            True
        """
        cdef int i

        self._n = len(g0)
        self._g = <int *> malloc(4*self._n*sizeof(int))

        if self._g == NULL:
            raise MemoryError("not able to allocate")

        for i from 0 <= i < self._n:
            self._g[i]     = g0[i]
            self._g[i+1*self._n] = g1[i]
            self._g[i+2*self._n] = g2[i]
            self._g[i+3*self._n] = g3[i]

        self._l_edges = {}
        self._i_edges = {}

    def nb_pillows(self):
        r"""
        Return the number of pillows.

        EXAMPLES::

            sage: from surface_dynamics.all import PillowcaseCover
            sage: PillowcaseCover([0], [0], [0], [0], as_tuple=True).nb_pillows()
            1
            sage: PillowcaseCover([2, 1, 3, 4], [1, 3, 2, 4], [4, 2, 3, 1]).nb_pillows()
            4
        """
        return self._n

    def __dealloc__(self):
        if self._g != NULL: free(self._g)

    def __hash__(self):
        r"""
        This hash is not very good. The returned value is always a multiple of
        2.

        TESTS::

            sage: from surface_dynamics.all import PillowcaseCover
            sage: p = PillowcaseCover([2, 1, 3, 4], [1, 3, 2, 4], [4, 2, 3, 1])
            sage: hash(p)
            -416597224

            sage: p = PillowcaseCover([1, 2, 4, 3], [3, 2, 1, 4], [2, 4, 3, 1])
            sage: hash(p)
            1452027672
        """
        cdef int i, h=0, b=17

        for i in range(4*self._n):
            h += self._g[i]*b
            b *= 50312161

        return h

    def __richcmp__(self, other, i):
        r"""
        Comparison

        EXAMPLES::

            sage: from surface_dynamics.all import PillowcaseCover

            sage: p3 = PillowcaseCover([2, 3, 1], [3, 2, 1], [2, 1, 3])
            sage: p4 = PillowcaseCover([1, 3, 2, 4], [3, 2, 4, 1], [2, 1, 3, 4])
            sage: p3 < p4
            True
            sage: p3 == p4 or p3 >= p4
            False

            sage: p = PillowcaseCover([2, 1, 3, 4], [1, 3, 2, 4], [4, 2, 3, 1])
            sage: q = PillowcaseCover([2, 1, 3, 4], [2, 3, 1, 4], [4, 3, 2, 1])
            sage: r = PillowcaseCover([2, 1, 3, 4], [3, 2, 1, 4], [3, 4, 2, 1])

            sage: p < q and q < r and p < r
            True
            sage: q > p and r > q and r > p
            True
            sage: p == p and p <= p and p >= p
            True
            sage: p == q or p == r or q == r
            False
            sage: q < p or r < q or r < p
            False
            sage: q <= p or r <= q or r <= p
            False
            sage: p >= q or q >= r or p >= r
            False
        """
         #0: <
         #1: <=
         #2: ==
         #3: !=
         #4: >
         #5: >=
        cdef PillowcaseCover_dense_pyx s
        cdef PillowcaseCover_dense_pyx o

        if not isinstance(self, PillowcaseCover_dense_pyx) or not isinstance(other, PillowcaseCover_dense_pyx):
            return TypeError("Pillowcase covers can only be compared to pillow case covers")

        s = <PillowcaseCover_dense_pyx> self
        o = <PillowcaseCover_dense_pyx> other

        # compare the number of squares
        if s._n != o._n:
            if i < 2: return s._n < o._n
            if i > 3: return s._n > o._n
            return i == 3

        # find the first index where self and other differ and make the
        # difference.
        cdef int j, test=0
        for j in range(4*s._n):
            if s._g[j] != o._g[j]:
                test = s._g[j] - o._g[j]
                break
        else:
            # all indices are equal
            return i != 0 and i != 3 and i != 4

        if i < 2: return test < 0
        if i > 3: return test > 0
        return i == 3

    cdef PillowcaseCover_dense_pyx _new_c(self, int * g):
        r"""
        Return an origami with given permutations.

        Beware that we assume that the created origami is in the same orbit
        under the action of PGL(2, Z).
        """
        cdef PillowcaseCover_dense_pyx other = PillowcaseCover_dense_pyx.__new__(PillowcaseCover_dense_pyx)

        other._n = self._n
        other._g = g

        other._l_edges = self._l_edges
        other._i_edges = self._i_edges

        return other

    def __copy__(self):
        r"""
        Return a copy of the origami

        EXAMPLES::

            sage: from surface_dynamics.all import Origami

            sage: o = Origami('(1,2)', '(1,3)')
            sage: oo = copy(o)
            sage: o == oo
            True
            sage: o is oo
            False
        """
        cdef int * g = <int *> malloc(4 * self._n * sizeof(int))

        memcpy(g, self._g, 4 * self._n * sizeof(int))

        return self._new_c(g)

    def g_tuple(self, int i):
        r"""
        Return a tuple.

        EXAMPLES::

            sage: from surface_dynamics.all import PillowcaseCover

            sage: p = PillowcaseCover([2, 1, 3, 4], [3, 2, 1, 4], [4, 2, 3, 1])
            sage: p.g_tuple(0)
            (1, 0, 2, 3)
            sage: p.g_tuple(1)
            (2, 1, 0, 3)
            sage: p.g_tuple(2)
            (3, 1, 2, 0)
            sage: p.g_tuple(3)
            (3, 0, 1, 2)

            sage: p.g_tuple(4)
            Traceback (most recent call last):
            ...
            IndexError: the index i (= 4) must be in {0, 1, 2, 3}
        """
        if i < 0 or i > 3:
            raise IndexError("the index i (= {}) must be in {{0, 1, 2, 3}}".format(i))
        return array_to_tuple(self._g+i*self._n, self._n)

    def degree(self):
        r"""
        The degree of the covering.

        EXAMPLES::

            sage: from surface_dynamics.all import PillowcaseCover

            sage: p = PillowcaseCover([2, 1, 3, 4], [3, 2, 1, 4], [4, 2, 3, 1])
            sage: p.degree()
            4
        """
        return self._n
