# -*- coding: utf-8 -*-
r"""
EkEkStar

AUTHORS:

 - Milton Minervino, 2017, initial version
 - Sébastien Labbé, July 6th 2017: added doctests, package, improve object
   oriented structure of the classes, multiplicity stored in the patch not
   in the faces. Fixed the creation of patches (linear time instead of
   quadratic time). Added a dozen of doctests.
 - Sébastien Labbé, March 28th, 2018: projection and plot of k-faces
   from a projection matrix. Computation of the projection on the
   contracting and expanding spaces directly from Minkowski embedding.
 - Sébastien Labbé, February 16th, 2021: Imported the module into slabbe
   package

.. TODO::

    - Fix some proper ordering for the faces (problems with doctests).

    - Patch should check that all its faces are consistent (same type
      length, dual or not, ambiant dimension)

    - Return an error if dual geosub is applied to not dual faces

    X Put a default projection inside the patch (from the geosub)

    - Add a function that creates the tiling

    - Add a method taking a substitution as input, computing the Markov
      partition (3d-cylinder of Rauzy fractals) from E_k and E_k^*,

    - Deal with reducible with neutral eigenvalues

    X Deal with neutral eigenvalues in the Minkowski projection function

    X Add this example (Hokaido): 1->12, 2->3, 3->4, 4->5, 5->1, an
      reducible substitution with neutral eigenvalues.

EXAMPLES:

The Tribonacci example::

    sage: from slabbe import GeoSub, kPatch, kFace
    sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
    sage: geosub = GeoSub(sub,2, presuf='prefix', dual=True)
    sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
    ....:             kFace((0,0,1),(1,3),dual=True),
    ....:             kFace((0,1,0),(2,1),dual=True),
    ....:             kFace((0,0,0),(3,1),dual=True)])
    sage: Q = geosub(P, 6)
    sage: Q
    Patch of 32 faces
    sage: _ = Q.plot()
    sage: Q.projection_matrix()
    [  1.00000000000000  -1.41964337760708 -0.771844506346038]
    [ 0.000000000000000  0.606290729207199  -1.11514250803994]

Hokaido example::

    sage: sub = {1:[1,2], 2:[3], 3:[4], 4:[5], 5:[1]}
    sage: geosub = GeoSub(sub, 3, dual=True)
    sage: F = kFace((0,0,0,0,0), (1,2,3), dual=True)
    sage: P = 1*F
    sage: P
    Patch: 1[(0, 0, 0, 0, 0), (1, 2, 3)]*
    sage: Q = geosub(P, 5)
    sage: Q
    Patch: 1[(1, 1, 0, 0, 0), (1, 2, 3)]* + -1[(1, 1, 0, 0, 0), (1, 2, 4)]* + 1[(1, 1, 0, 0, 0), (1, 2, 5)]*
    sage: _ = Q.plot()
    sage: Q.projection_matrix()
    [  1.00000000000000  -1.66235897862237  0.784920145499027 0.215079854500973 -0.877438833123346]
    [ 0.000000000000000  0.562279512062301  -1.30714127868205 1.30714127868205  -0.744861766619744]

"""
#*****************************************************************************
#       Copyright (C) 2017 Milton Minervino
#       Copyright (C) 2017-2021 Sébastien Labbé <slabqc@gmail.com>
#
#  Distributed under the terms of the GNU General Public License version 2 (GPLv2)
#
#  The full text of the GPLv2 is available at:
#
#                  http://www.gnu.org/licenses/
#*****************************************************************************
import itertools
from collections import Counter
from numpy import argsort
from sage.misc.cachefunc import cached_method
from sage.structure.sage_object import SageObject
from sage.rings.integer_ring import ZZ
from sage.modules.free_module_element import vector, zero_vector
from sage.rings.all import CC
from sage.combinat.words.morphism import WordMorphism
from sage.combinat.permutation import Permutation
from sage.rings.number_field.number_field import NumberField
from sage.plot.colors import Color, rainbow
from sage.plot.graphics import Graphics
from sage.plot.polygon import polygon2d
from sage.plot.line import line

##########
#  Classes
##########
class kFace(SageObject):
    r"""

    INPUT:

    - ``v`` -- vector
    - ``t`` -- tuple, type
    - ``dual`` -- bool (default:``False``)
    - ``color`` -- string (default:``None``)

    EXAMPLES:

    Face based at (0,0,0) of type (1,2)::

        sage: from slabbe import kFace
        sage: F = kFace((0,0,0),(1,2))
        sage: F
        [(0, 0, 0), (1, 2)]
        
    Face based at (0,0,0) of type (3,1)::

        sage: kFace((0,0,0),(3,1))
        [(0, 0, 0), (3, 1)]
        
    Dual face based at (0,0,0,0) of type (1)::
        
        sage: kFace((0,0,0,0),(1), dual=True)
        [(0, 0, 0, 0), (1,)]*

    Operations::

        sage: F = kFace((0,0,0),(1,2))
        sage: F
        [(0, 0, 0), (1, 2)]
        sage: -2 * F.dual()
        Patch: -2[(0, 0, 0), (1, 2)]*
        
    Color of a face::
    
        sage: F = kFace((0,0,0),(1,2))
        sage: F.color()
        RGB color (1.0, 0.0, 0.0)

        sage: F = kFace((0,0,0),(1,2),color='green')
        sage: F.color()
        RGB color (0.0, 0.5019607843137255, 0.0)
        
    """
    def __init__(self, v, t, dual=False, color=None):
        r"""
        EXAMPLES::

            sage: from slabbe import kFace
            sage: F = kFace((0,0,0),(1,2))
            sage: F
            [(0, 0, 0), (1, 2)]
        """
        self._vector = (ZZ**len(v))(v)
        self._vector.set_immutable()
        self._dual = dual
        
        if t in ZZ:
            self._type = (t,)
        else:
            self._type = t

        if not all((tt in ZZ and 1 <= tt <= len(v)) for tt in self._type):
            raise ValueError('The type must be a tuple of integers between 1 and {}'.format(len(v)))
        
        if color is not None:
            self._color = Color(color)
        else:
            sorted_types = list(itertools.combinations(range(1,len(v)+1),len(self._type)))
            Col = rainbow(len(sorted_types))
            D = dict(zip(sorted_types,Col))
            self._color = Color(D.get(self.sorted_type(), 'black'))

    def vector(self):
        return self._vector

    def dimension(self):
        r"""
        Return the dimension of the ambiant space.

        EXAMPLES::

            sage: from slabbe import kFace
            sage: F = kFace((0,0,0),(1,2))
            sage: F.dimension()
            3
        """
        return len(self._vector)

    def face_dimension(self):
        r"""
        Return the dimension of the face.

        EXAMPLES::

            sage: from slabbe import kFace
            sage: F = kFace((0,0,0),(1,2))
            sage: F.face_dimension()
            2

        ::

            sage: F = kFace((0,0,0), (1,2), dual=True)
            sage: F.face_dimension()
            1
        """
        if self.is_dual():
            return self.dimension() - len(self._type)
        else:
            return len(self._type)

    def type(self):
        return self._type

    def is_dual(self):
        return self._dual


    def sorted_type(self):
        return tuple(sorted(self._type))


    def color(self):
        return self._color
        
        
            
        
    @cached_method
    def sign(self):
        r"""
        EXAMPLES::
        
            sage: from slabbe import kFace
            sage: kFace((0,0,0,0,0),(1,2,3,4,5)).sign()
            1
            sage: kFace((0,0,0,0,0),(1,2,3,4,4)).sign()
            0
            sage: kFace((0,0,0,0,0),(1,2,3,5,4)).sign()
            -1
        """
        sorted_type = self.sorted_type()
        if all(sorted_type[i] < sorted_type[i+1] for i in range(len(sorted_type)-1)):
            p = argsort(self._type) + 1
            return Permutation(p).sign()
        else:
            return 0

    def __repr__(self):
        r"""
        String representation.

        EXAMPLES::

            sage: from slabbe import kFace
            sage: kFace((0,0,0,0,0),(1,2,3,4,5))
            [(0, 0, 0, 0, 0), (1, 2, 3, 4, 5)]
            sage: kFace((0,0,0,0,0),(1,2,3,4,4))
            [(0, 0, 0, 0, 0), (1, 2, 3, 4, 4)]
            sage: kFace((0,0,0,0,0),(1,2,3,5,4))
            [(0, 0, 0, 0, 0), (1, 2, 3, 5, 4)]

        Dual face::

            sage: kFace((0,0,0), (1,2,3), dual=True)
            [(0, 0, 0), (1, 2, 3)]*
        """
        d = '*' if self.is_dual() else ''
        return "[{}, {}]{}".format(self.vector(), self.type(), d)

    def __lt__(self, other):
        if not isinstance(other, kFace):
            return NotImplemented
        elif self.vector() < other.vector():
            return True
        elif self.vector() == other.vector():
            if self.type() < other.type():
                return True
            elif self.type() == other.type(): 
                if self.is_dual() < other.is_dual():
                    return True
                else:
                    return False
            else:
                return False
        else:
            return False

    def __eq__(self, other):
        return (isinstance(other, kFace) and
                self.vector() == other.vector() and
                self.type() == other.type() and 
                self.is_dual() == other.is_dual())

    @cached_method
    def __hash__(self):
        return hash((self.vector(), self.type(), self.is_dual()))

    def __add__(self, other):
        r"""
        EXAMPLES::
        
            sage: from slabbe import kFace
            sage: kFace((0,0,0),(1,3)) + kFace((0,0,0),(3,1))
            Empty patch
            sage: kFace((0,0,0),(1,3)) + kFace((0,0,0),(1,3))
            Patch: 2[(0, 0, 0), (1, 3)]

        This method allows also to add a k-face with a k-patch::

            sage: from slabbe import kPatch
            sage: P = kPatch([kFace((0,0,0),(1,3))])
            sage: F = kFace((0,0,0), (2,3))
            sage: F + P
            Patch: 1[(0, 0, 0), (1, 3)] + 1[(0, 0, 0), (2, 3)]

        Thus this works::

            sage: F + F
            Patch: 2[(0, 0, 0), (2, 3)]
            sage: F + F + F
            Patch: 3[(0, 0, 0), (2, 3)]

        TESTS:

        This use to be a bug::

            sage: kFace((0,0,0),(1,2)) + kFace((0,0,1),(3,1)) + kFace((13,23,34),(1,1))
            Patch: 1[(0, 0, 0), (1, 2)] + -1[(0, 0, 1), (1, 3)]
        """
        if isinstance(other, kFace):
            return kPatch([self, other])
        else:
            return kPatch([self]).union(other)

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

            sage: from slabbe import kFace
            sage: F = kFace((0,0,0),(1,3))
            sage: F
            [(0, 0, 0), (1, 3)]
            sage: -F
            Patch: -1[(0, 0, 0), (1, 3)]

        """
        return kPatch({self:-1})

    def __rmul__(self, coeff):
        r"""
        EXAMPLES::

            sage: from slabbe import kFace
            sage: F = kFace((0,0,0),(1,3))
            sage: F
            [(0, 0, 0), (1, 3)]
            sage: 4 * F
            Patch: 4[(0, 0, 0), (1, 3)]
            sage: -2 * F
            Patch: -2[(0, 0, 0), (1, 3)]

        Multiplication by zero gives the empty patch::

            sage: 0 * F
            Empty patch

        """
        return kPatch({self:coeff})

    def dual(self):
        r"""
        Return the dual face.

        EXAMPLES::

            sage: from slabbe import kFace
            sage: kFace((0,0,0),(1,3))
            [(0, 0, 0), (1, 3)]
            sage: kFace((0,0,0),(1,3)).dual()
            [(0, 0, 0), (1, 3)]*
            sage: kFace((0,0,0),(1,3)).dual().dual()
            [(0, 0, 0), (1, 3)]

        """
        return kFace(self.vector(), self.type(), dual=not self.is_dual(), 
                     color=self.color())

    def contour(self):
        r"""
        Return the face contour.

        If this is an edge, it returns the two end points. If this is a
        losange, it returns the four corners.

        OUTPUT:

        - list of vectors in the Z-module

        EXAMPLES::

            sage: from slabbe import kFace
            sage: kFace((0,0,0),(1,3)).contour()
            [(0, 0, 0), (1, 0, 0), (1, 0, 1), (0, 0, 1)]
            sage: kFace((0,0,0),(2,)).contour()
            [(0, 0, 0), (0, 1, 0)]
            sage: kFace((0,0,0),(2,), dual=True).contour()
            [(0, 0, 0), (1, 0, 0), (1, 0, 1), (0, 0, 1)]
            sage: kFace((0,0,0),(2,3), dual=True).contour()
            [(0, 0, 0), (1, 0, 0)]

        A 2-dimensional dual face in `\RR^5`::

            sage: F = kFace((0,0,0,0,0), (1,2,3), dual=True)
            sage: F
            [(0, 0, 0, 0, 0), (1, 2, 3)]*
            sage: F.contour()
            [(0, 0, 0, 0, 0), (0, 0, 0, 1, 0), (0, 0, 0, 1, 1), (0, 0, 0, 0, 1)]

        """
        v = self.vector()
        R = ZZ**self.dimension()
        e = {i+1:gen for i,gen in enumerate(R.gens())}
        if self.is_dual():
            indices = range(1, self.dimension()+1)
            t = set(indices) - set(self.type())
        else:
            t = self.type()
        if self.face_dimension() == 1:
            c, = t
            return [v, v+e[c]]
        elif self.face_dimension() == 2:
            a,b = t
            return [v, v+e[a], v+e[a]+e[b], v+e[b]]
        else:
            raise NotImplementedError("Contour is implemented only for "
                    "face that are 1-dimensional edges or 2-dimensional,"
                    " {}-dimensional".format(self.face_dimension()))

    def contour_projection(self, M): 
        r"""
        Return the projection of the face contour.

        INPUT:

        - ``M`` -- projection matrix

        EXAMPLES::

            sage: from slabbe import kFace, GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: M = geosub.projection_matrix()

        This illustrates the case with faces of dimension 1 with a field
        with one complex embedding::

            sage: kFace((10,21,33), (1,2), dual=True).contour_projection(M)
            [(-45.2833796391679, -24.0675974519667),
             (-46.0552241455140, -25.1827399600067)]

        This illustrates the case with faces of dimension 2 with a field
        with one complex embedding::

            sage: kFace((10,21,33), (1,), dual=True).contour_projection(M)
            [(-45.2833796391679, -24.0675974519667),
             (-46.7030230167750, -23.4613067227595),
             (-47.4748675231211, -24.5764492307995),
             (-46.0552241455140, -25.1827399600067)]

        Brun substitutions ``[123,132,213,231]`` gives a incidence matrix with
        totally real eigenvalues::

            sage: from slabbe.mult_cont_frac import Brun
            sage: algo = Brun()
            sage: S = algo.substitutions()
            sage: sub = prod([S[a] for a in [123,132,213,231]])
            sage: sub
            WordMorphism: 1->1323, 2->23, 3->3231323

        This illustrates the case with faces of dimension 1 with totally
        real field projected in a contracting space of dimension 2::

            sage: sub = {1: [1,3,2,3], 2: [2,3], 3: [3,2,3,1,3,2,3]}
            sage: geosub = GeoSub(sub, 2, dual=True)
            sage: M = geosub.projection_matrix()
            sage: kFace((10,21,33), (1,2), dual=True).contour_projection(M)
            [(6.690365529225190287265975075034, -1.500190036950057598982635871389),
             (5.443385925507723226215965307025, -1.055148169037428790404830742396)]

        This illustrates the case with faces of dimension 1 with totally
        real field projected in a contracting space of dimension 1::

            sage: sub = {1:[1,2,3,3,3,3], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: M = geosub.projection_matrix()
            sage: kFace((7,-3,4),(1,2), dual=True).contour_projection(M)
            [(-65.28494960003319740280849294991),
             (-70.02977567771512068843031399068)]

        This illustrates the case with faces of dimension 2 with a field
        with one complex embedding::

            sage: sub = {1:[1,1,4], 
            ....:   2:[1,2,4,1,2,4,1,3,4], 
            ....:   3:[1,2,4,1,3,4,], 
            ....:   4:[1,2,4,1,2,4,1,3,4,1,4]}
            sage: geosub = GeoSub(sub, 2)
            sage: M = geosub.projection_matrix()
            sage: kFace((10,21,33,-7), (1,2)).contour_projection(M)
            [(21.18350167207436655863368355998, -150.7254323268995855552615832997),
             (20.18350167207436655863368355998, -151.7254323268995855552615832997),
             (21.08776078027425227765222077178, -155.8643948778810657648963968540),
             (22.08776078027425227765222077178, -154.8643948778810657648963968540)]

        This illustrates the case with faces of dimension 2 in a
        totally real field::

            sage: sub = {1:[1,2,4], 2:[1,2,2,4], 3:[1,2,4,3,3,4], 4:[1,2,4,3,4]}
            sage: geosub = GeoSub(sub, 2)
            sage: M = geosub.projection_matrix()
            sage: kFace((10,21,33,-7), (1,2)).contour_projection(M)
            [(32.57858448022453517201706283181, -112.0669861296918881523709190477),
             (31.57858448022453517201706283181, -113.0669861296918881523709190477),
             (29.38505739489348123345652443465, -114.3619490289834872642844850711),
             (30.38505739489348123345652443465, -113.3619490289834872642844850711)]

        The next example illustrates the case where the dilating space is
        of dimension one (Pisot case) and the faces are of dimension 2 in
        R^3 so the faces are projected on a one-dimensional space::

            sage: sub = {1: [1,3,2,3], 2: [2,3], 3: [3,2,3,1,3,2,3]}
            sage: geosub = GeoSub(sub, 2)
            sage: M = geosub.projection_matrix()
            sage: kFace((10,13,27), (1,2)).contour_projection(M)
            [(-64.43786314959480732826099193032),
             (-65.43786314959480732826099193032),
             (-65.88290501750743613683879705932),
             (-64.88290501750743613683879705932)]

        """
        return [M*c for c in self.contour()]

    def plot(self, M, color=None):
        r"""
        INPUT:

        - ``M`` -- projection matrix
        - ``color`` -- string or None

        EXAMPLES::

            sage: from slabbe import kFace, GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: M = geosub.projection_matrix()
            sage: _ = kFace((10,21,33), (1,2), dual=True).plot(M)
        """
        if color is None:
            color = self._color
        L = self.contour_projection(M)
        if self.face_dimension() == 1:
            return line(L, color=color, thickness=3) 
        elif self.face_dimension() == 2:
            return polygon2d(L, color=color, thickness=.1, alpha=.8)     
        else:
            raise NotImplementedError("Plotting is implemented only for patches in two or three dimensions.")

class kPatch(SageObject):
    r"""
    EXAMPLES::

        sage: from slabbe import kPatch, kFace
        sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
        ....:             kFace((0,0,1),(1,3),dual=True),
        ....:             kFace((0,1,0),(2,1),dual=True),
        ....:             kFace((0,0,0),(3,1),dual=True)])
        sage: P
        Patch: 1[(0, 0, 0), (1, 2)]* + -1[(0, 0, 0), (1, 3)]* + 1[(0, 0, 1), (1, 3)]* + -1[(0, 1, 0), (1, 2)]*
    """
    def __init__(self, faces):
        r"""
        EXAMPLES::

        TESTS:

        Cancellation because of ordering of the type::

            sage: from slabbe import kFace, kPatch
            sage: L = [kFace((0,0,0),(1,3)), kFace((0,0,0),(3,1))]
            sage: kPatch(L)
            Empty patch

        Repetition in the list::

            sage: L = [kFace((0,0,0),(1,3)), kFace((0,0,0),(1,3))]
            sage: kPatch(L)
            Patch: 2[(0, 0, 0), (1, 3)]
        """
        # Compute the formal sum with support on canonical faces
        self._faces = Counter()
        if isinstance(faces, list):
            for f in faces:
                canonical = kFace(f.vector(), f.sorted_type(), dual=f.is_dual(), color=f.color())
                self._faces[canonical] += f.sign()
        else:
            for (f,m) in faces.items():
                canonical = kFace(f.vector(), f.sorted_type(), dual=f.is_dual(), color=f.color())
                self._faces[canonical] += m*f.sign()

        # Remove faces with multiplicity zero from the formal sum
        for f, m in list(self._faces.items()):
            if m == 0:
                del self._faces[f]

        # Default projection matrix
        self._projection_matrix = None
            
    def __len__(self):
        return len(self._faces)

    def __iter__(self):
        return iter(self._faces.items())
       
    def __add__(self, other):
        r"""
        EXAMPLES::

            sage: from slabbe import kFace, kPatch
            sage: P = kPatch([kFace((0,0,0),(1,3))])
            sage: Q = kPatch([kFace((0,0,0),(3,2))])
            sage: P + Q
            Patch: 1[(0, 0, 0), (1, 3)] + -1[(0, 0, 0), (2, 3)]
            sage: P + P
            Patch: 2[(0, 0, 0), (1, 3)]

        When there are cancellations::

            sage: R = kPatch([kFace((0,0,0),(3,1))])
            sage: P + R
            Empty patch
            sage: R + P
            Empty patch

        A k-patch plus a k-face::

            sage: F = kFace((0,0,0), (2,3))
            sage: P + F
            Patch: 1[(0, 0, 0), (1, 3)] + 1[(0, 0, 0), (2, 3)]

        Works with dual k-faces::

            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True),
            ....:             kFace((0,1,0),(2,1),dual=True)])
            sage: Q = kPatch([kFace((0,1,0),(2,1),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: P + Q
            Patch: 1[(0, 0, 0), (1, 2)]* + -1[(0, 0, 0), (1, 3)]* + 1[(0, 0, 1), (1, 3)]* + -2[(0, 1, 0), (1, 2)]*

        """
        if isinstance(other, kFace):
            return self + kPatch({other:1})
        elif isinstance(other, kPatch):
            C = Counter()
            for (f,m) in self._faces.items():
                C[f] += m
            for (f,m) in other._faces.items():
                C[f] += m
            return kPatch(C)
        else:
            raise TypeError("Can not add {} with {}".format(self,other))

    def __rmul__(self, coeff):
        r"""
        INPUT:

        - ``coeff`` -- integer

        EXAMPLES::

            sage: from slabbe import kPatch, kFace
            sage: P = kPatch([kFace((0,0,0),(1,3))])
            sage: P
            Patch: 1[(0, 0, 0), (1, 3)]

        ::

            sage: -4 * P
            Patch: -4[(0, 0, 0), (1, 3)]
            sage: -1 * P
            Patch: -1[(0, 0, 0), (1, 3)]
            sage: 1 * P
            Patch: 1[(0, 0, 0), (1, 3)]

        Multiplication by zero gives the empty patch::

            sage: 0 * P
            Empty patch

        Currently, it is not forbidden to use non integral coefficients::

            sage: -4.3 * P
            Patch: -4.30000000000000[(0, 0, 0), (1, 3)]

        TESTS:

        Right multiplication is not defined::

            sage: P * 2
            Traceback (most recent call last):
            ...
            TypeError: unsupported operand parent(s) for *: '<class '...EkEkstar.kPatch'>' and 'Integer Ring'

        """
        D = {f:coeff*m for (f,m) in self._faces.items()}
        return kPatch(D)

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

            sage: from slabbe import kPatch, kFace
            sage: P = kPatch([kFace((0,0,0),(1,3))])
            sage: P
            Patch: 1[(0, 0, 0), (1, 3)]
            sage: -P
            Patch: -1[(0, 0, 0), (1, 3)]

        """
        D = {f:-m for (f,m) in self._faces.items()}
        return kPatch(D)

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

            sage: from slabbe import kPatch, kFace
            sage: P = kPatch([kFace((0,1,0),(1,2)), kFace((0,0,0),(1,3))])
            sage: P
            Patch: 1[(0, 0, 0), (1, 3)] + 1[(0, 1, 0), (1, 2)]
            sage: P.dual()
            Patch: 1[(0, 0, 0), (1, 3)]* + 1[(0, 1, 0), (1, 2)]*

        """
        D = {f.dual():m for (f,m) in self._faces.items()}
        return kPatch(D)

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

            sage: from slabbe import kFace, kPatch
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True),
            ....:             kFace((0,1,0),(2,1),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: P.dimension()
            3
        """
        try:
            f0 = next(iter(self._faces))
        except StopIteration:
            return None
        else:
            return f0.dimension()

    def projection_matrix(self, M=None):
        r"""
        Set or get the default projection matrix

        INPUT:

        - ``M`` -- projection matrix or ``None``

        OUTPUT:

        - ``None`` or a matrix

        EXAMPLES::

            sage: from slabbe import kFace, kPatch
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: P.projection_matrix() is None
            True
            sage: L = [-0.866025403784439, 0.866025403784439, 0.000000000000000,
            ....:      -0.500000000000000, -0.500000000000000, 1.00000000000000]
            sage: M = matrix(2, L)
            sage: P.projection_matrix(M)
            sage: P.projection_matrix()
            [-0.866025403784439  0.866025403784439  0.000000000000000]
            [-0.500000000000000 -0.500000000000000   1.00000000000000]

        """
        if M is None:
            return self._projection_matrix
        else:
            self._projection_matrix = M

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

            sage: from slabbe import kFace, kPatch
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True),
            ....:             kFace((0,1,0),(2,1),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: P
            Patch: 1[(0, 0, 0), (1, 2)]* + -1[(0, 0, 0), (1, 3)]* + 1[(0, 0, 1), (1, 3)]* + -1[(0, 1, 0), (1, 2)]*

        With multiplicity::

            sage: P = kPatch({kFace((0,0,0),(1,2),dual=True):11,
            ....:             kFace((0,0,1),(1,3),dual=True):22,
            ....:             kFace((0,1,0),(2,1),dual=True):33,
            ....:             kFace((0,0,0),(3,1),dual=True):-44})
            sage: P
            Patch: 11[(0, 0, 0), (1, 2)]* + 44[(0, 0, 0), (1, 3)]* + 22[(0, 0, 1), (1, 3)]* + -33[(0, 1, 0), (1, 2)]*

        Empty patch::

            sage: kPatch([])
            Empty patch
        """
        if len(self) == 0:
            return "Empty patch"
        elif len(self) <= 30:
            L = ["{}{}".format(m,f) for (f,m) in sorted(self)]
            return "Patch: %s" % ' + '.join(L)
        else:
            return "Patch of %s faces"%len(self)
        
    def union(self, other):
        r"""
        INPUT:

        - ``other`` -- a face, a patch or a list of faces

        EXAMPLES::

            sage: from slabbe import kFace, kPatch
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True)])
            sage: f = kFace((0,1,0),(2,1),dual=True)
            sage: g = kFace((0,0,0),(3,1),dual=True)

        A patch union with a face::

            sage: P.union(f)
            Patch: 1[(0, 0, 0), (1, 2)]* + 1[(0, 0, 1), (1, 3)]* + -1[(0, 1, 0), (1, 2)]*

        A patch union with a patch::

            sage: P.union(P)
            Patch: 2[(0, 0, 0), (1, 2)]* + 2[(0, 0, 1), (1, 3)]*

        A patch union with a list of faces::

            sage: P.union([f,g])
            Patch: 1[(0, 0, 0), (1, 2)]* + -1[(0, 0, 0), (1, 3)]* + 1[(0, 0, 1), (1, 3)]* + -1[(0, 1, 0), (1, 2)]*

        """
        if isinstance(other, kFace):
            return self + kPatch([other])
        elif isinstance(other, kPatch):
            return self + other
        else:
            return self + kPatch(other)
        
    def plot(self, M=None, color=None):
        r"""
        INPUT:

        - ``M`` -- projection matrix or ``None``. If ``None``, it uses the
          default projection matrix.
        - ``color`` -- string or None

        EXAMPLES::

            sage: from slabbe import kFace, kPatch, GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: P = kPatch([kFace((0,0,0),(1,2),dual=True),
            ....:             kFace((0,0,1),(1,3),dual=True),
            ....:             kFace((0,1,0),(2,1),dual=True),
            ....:             kFace((0,0,0),(3,1),dual=True)])
            sage: M = geosub.projection_matrix()
            sage: _ = P.plot(M)

        A patch created from the application of a geometric substitution
        remembers the canonical projection matrix of the geo. subst. as its
        default projection. So plot may be called with no argument::

            sage: Q = geosub(P)
            sage: _ = Q.plot()

        The default projection matrix in this case is::

            sage: Q.projection_matrix()
            [  1.00000000000000  -1.41964337760708 -0.771844506346038]
            [ 0.000000000000000  0.606290729207199  -1.11514250803994]

        """
        if M is None:
            M = self.projection_matrix()
            if M is None:
                raise ValueError('No default projection matrix was given. '
                        'You need to provide one as input.')
        G = Graphics()
        for face,m in self:
            G += face.plot(M, color)
        G.set_aspect_ratio(1)
        return G

def ps_automaton(sub, presuf):
    r"""
    Return the prefix or suffix automaton

    (related to the prefix-suffix automaton).

    INPUT:

    - ``sub`` -- dict, 1 dimensional substitution
    - ``presuf`` -- string, ``"prefix"`` or ``"suffix"``

    OUTPUT:

        dict

    EXAMPLES::

        sage: from slabbe.EkEkstar import ps_automaton
        sage: m = {1:[2,1], 2:[2,1,1]}
        sage: ps_automaton(m, "prefix")
        {1: [(2, []), (1, [2])], 2: [(2, []), (1, [2]), (1, [2, 1])]}
        sage: ps_automaton(m, 'suffix')
        {1: [(2, [1]), (1, [])], 2: [(2, [1, 1]), (1, [1]), (1, [])]}

    """
    d = {}
    for key,value in sub.items():
        L = []
        for j,letter in enumerate(value):
            if presuf == "prefix":
                L.append((letter, value[:j]))
            elif presuf == "suffix":
                L.append((letter, value[j+1:])) 
        d[key] = L  
    return d             

def ps_automaton_inverted(sub, presuf):
    r"""
    Return the prefix or suffix automaton with inverted edges.

    (related to the prefix-suffix automaton).

    INPUT:

    - ``sub`` -- dict, 1 dimensional substitution
    - ``presuf`` -- string, ``"prefix"`` or ``"suffix"``

    OUTPUT:

        dict

    EXAMPLES::

        sage: from slabbe.EkEkstar import ps_automaton_inverted
        sage: m = {2:[2,1,1], 1:[2,1]}
        sage: ps_automaton_inverted(m, "prefix")
        {1: [(1, [2]), (2, [2]), (2, [2, 1])], 2: [(1, []), (2, [])]}
        sage: ps_automaton_inverted(m, 'suffix')
        {1: [(1, []), (2, []), (2, [1])], 2: [(1, [1]), (2, [1, 1])]}

    """
    Gr = ps_automaton(sub, presuf)
    d = {}
    for a in sub:
        L = []
        for i,Gr_i in Gr.items():
            sub_i = sub[i]
            L.extend((i,Gr_i[j][1]) for j,sub_i_j in enumerate(sub_i) 
                                    if sub_i_j == a)
        L.sort()
        d[a] = L    
    return d

def abelian(L, alphabet):
    r"""
    EXAMPLES::

        sage: from slabbe.EkEkstar import abelian
        sage: abelian([1,0,1,2,3,1,1,2,2], [0,1,2,3])
        (1, 4, 3, 1)
    """
    return vector([L.count(i) for i in alphabet])

class GeoSub(SageObject):
    r"""
    INPUT:

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

    EXAMPLES::

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

    def is_dual(self) -> bool:
        return self._dual

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

            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.field()
            Number Field in b with defining polynomial x^3 - x^2 - x - 1

        When the characteristic polynomial is reducible (Hokaido example)::

            sage: sub = {1:[1,2], 2:[3], 3:[4], 4:[5], 5:[1]}
            sage: geosub = GeoSub(sub, 3, dual=True)
            sage: geosub.field()
            Number Field in b with defining polynomial x^3 - x - 1
        """
        M = self._sigma.incidence_matrix()
        b1 = max(M.eigenvalues(), key=abs)
        f = b1.minpoly()
        K = NumberField(f, 'b')
        return K
        
    def gen(self):
        r"""
        EXAMPLES::

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

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

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

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

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

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

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

        ::

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

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

    def projection_matrix(self, prec=None):
        r"""
        Return the Minkowski projection to the contracting (or expanding)
        space.

        INPUT:

        - ``prec`` -- integer (default:``None``), the precision. The
          computations will use ``RealField(prec)`` or ``RDF`` if ``prec``
          is ``None`` or the field of algebraic numbers ``QQbar`` (or it
          subfield ``AA`` of algebraic reals) if ``prec`` is infinity.

        EXAMPLES::

            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: GeoSub(sub, 2).projection_matrix()      # tol
            [ -1.00000000000000 -0.839286755214161 -0.543689012692076]
            sage: GeoSub(sub, 2, dual=True).projection_matrix()
            [  1.00000000000000  -1.41964337760708 -0.771844506346038]
            [ 0.000000000000000  0.606290729207199  -1.11514250803994]

        With algebraic coefficients::

            sage: GeoSub(sub, 2, dual=True).projection_matrix(prec=oo)
            [                   1  -1.419643377607081?  -0.7718445063460381?]
            [                   0  0.6062907292071993?   -1.115142508039938?]

        """
        K = self.field()
        vb = self.dominant_left_eigenvector()
        from slabbe.matrices import Minkowski_projection_triple
        P,Q,R = Minkowski_projection_triple(K, vb, prec=prec)
        if self.is_dual():
            return Q
        else:
            return -P
    def prefix_suffix_automaton(self):
        r"""
        EXAMPLES::

            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.prefix_suffix_automaton()
            {1: [(1, []), (2, [1])], 2: [(1, []), (3, [1])], 3: [(1, [])]}

        """
        if self.is_dual():
            return ps_automaton_inverted(self._sigma_dict, self._presuf)
        else:
            return ps_automaton(self._sigma_dict, self._presuf)

    @cached_method
    def base_iter(self):
        r"""
        EXAMPLES::
            
            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.base_iter()
            {(1, 2): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (2,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (1, 3)],
            [(1, 0, 0), (2, 1)],
            [(2, 0, 0), (2, 3)]],
            (1, 3): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (2,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (2, 1)]],
            (2, 3): [[(0, 0, 0), (1,)],
            [(1, 0, 0), (3,)],
            [(0, 0, 0), (1, 1)],
            [(1, 0, 0), (3, 1)]]}
        """
        automaton = self.prefix_suffix_automaton()
        X = {}
        S = self._sigma_dict.keys()
        for x in itertools.combinations(S, self._k):
            X[x] = []
            bigL = []
            for y in x:
                bigL.append(automaton[y])
                for el in itertools.product(*bigL):
                    z = []
                    w = []
                    for el_i in el:
                        z.extend(el_i[1])
                        w.append(el_i[0])
                    if self.is_dual():
                        M = self._sigma.incidence_matrix()
                        X[x].append([-M.inverse()*abelian(z, S),tuple(w)])
                    else:
                        X[x].append([abelian(z, S),tuple(w)])
        return X
           
    def __call__(self, patch, iterations=1):
        r"""
        EXAMPLES::

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

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

            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: E = GeoSub(sub,2)
            sage: E.matrix()
            [1 1 1]
            [1 0 0]
            [0 1 0]
        """
        if self.is_dual():
            return self._sigma.incidence_matrix().inverse()
        else:
            return self._sigma.incidence_matrix()
        
    def _call_on_face(self, face, color=None):
        r"""
        INPUT:

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

        OUTPUT:

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

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

        The available face type are::

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

        For each we get::

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

        """
        x_new = self.matrix() * face.vector()
        #t = face.type()
        t = face.sorted_type()
        if self.is_dual():
            return {kFace(x_new - vv, tt, dual=self.is_dual()):(-1)**(sum(t)+sum(tt))*face.sign()
                    for (vv, tt) in self.base_iter()[t] if len(tt) == self._k}
        else:
            return {kFace(x_new + vv, tt, dual=self.is_dual()):face.sign()
                    for (vv, tt) in self.base_iter()[t] if len(tt) == self._k}
                
    def __repr__(self):
        r"""
        EXAMPLES::
            
            sage: from slabbe import GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: GeoSub(sub, 2)
            E_2(1->12, 2->13, 3->1)
            sage: GeoSub(sub, 2, dual=True)
            E*_2(1->12, 2->13, 3->1)
        """
        if self.is_dual(): 
            return "E*_%s(%s)" % (self._k,str(self._sigma))
        else: 
            return "E_%s(%s)" % (self._k,str(self._sigma))
        

