Ticket #11010: trac_11010-subword_complex-cs.patch

File trac_11010-subword_complex-cs.patch, 35.1 KB (added by stumpc5, 6 years ago)
  • sage/combinat/all.py

    # HG changeset patch
    # User Christian Stump <christian.stump at gmail.com>
    # Date 1358962330 0
    # Node ID d0a68c3d78dd72bec5291440b4416a06d5965d32
    # Parent  08d8d94ade4f30878604278e90d731e88f9a34af
    [mq]: trac_11010-subword_complex-cs.patch
    
    diff --git a/sage/combinat/all.py b/sage/combinat/all.py
    a b from kazhdan_lusztig import KazhdanLuszt 
    133133from degree_sequences import DegreeSequences
    134134
    135135from cyclic_sieving_phenomenon import CyclicSievingPolynomial, CyclicSievingCheck
     136from subword_complex import SubwordComplex
    136137
    137138from sidon_sets import sidon_sets
  • new file sage/combinat/subword_complex.py

    diff --git a/sage/combinat/subword_complex.py b/sage/combinat/subword_complex.py
    new file mode 100644
    - +  
     1r"""
     2Subword complex
     3
     4AUTHORS:
     5
     6- Christian Stump
     7"""
     8#*****************************************************************************
     9#       Copyright (C) 2012      Christian Stump <christian.stump@gmail.com>
     10#
     11#  Distributed under the terms of the GNU General Public License (GPL)
     12#  The full text of the GPL is available at:
     13#
     14#                  http://www.gnu.org/licenses/
     15#*****************************************************************************
     16from sage.misc.cachefunc import cached_method
     17from sage.modules.free_module_element import vector
     18from sage.homology.simplicial_complex import SimplicialComplex, Simplex
     19from sage.combinat.combination import Combinations
     20from sage.geometry.cone import Cone
     21from sage.structure.element import Element
     22from sage.structure.parent import Parent
     23from sage.matrix.all import Matrix
     24from copy import copy
     25from sage.misc.classcall_metaclass import ClasscallMetaclass
     26
     27class SubwordComplex(SimplicialComplex,Parent):
     28    r"""
     29    Fix a Coxeter system (W,S). The subword complex Delta(Q,w) associated to a word Q in S and an element w in W
     30    is defined to be the simplicial complex with facets being complements Q/Q' for which Q' is a reduced expression for w
     31
     32    A subword complex is a shellable sphere if and only if the Demazure product of Q equals w,
     33    otherwise it is a shellable ball.
     34
     35    EXAMPLES::
     36
     37        As an example, dual associahedra are subword complexes in type A_{n-1} given by the
     38        word [n-1,...,1,n-1,...,1,n-1,...,2,...,n-1,n-2,n-1] and the permutation w_0
     39
     40        sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     41        sage: w = W.from_reduced_word([1,2,1])
     42        sage: C = SubwordComplex([2,1,2,1,2],w); C
     43        Subword complex of type ['A', 2] for Q = [2, 1, 2, 1, 2] and pi = [1, 2, 1]
     44
     45        sage: C.facets()
     46        [(0, 1), (0, 4), (1, 2), (2, 3), (3, 4)]
     47    """
     48    def __init__(self, Q, w, g=0, algorithm="inductive"):
     49        W = w.parent()
     50        I = W.index_set()
     51        if not all( i in I for i in Q ):
     52            raise ValueError, "All elements in Q = %s must be contained in the index set %s"%(Q,W.index_set())
     53        if algorithm != "inductive" and not g == 0:
     54            raise ValueError("The genus can only be nonzero if the inductive algorithm is used.")
     55        Vs = range(len(Q))
     56        self._Q = Q
     57        self._pi = w
     58        self._g = g
     59        if algorithm == "inductive":
     60            Fs = _construct_facets(Q,w,g=g)
     61        elif algorithm == "greedy":
     62            Fs, Rs = _greedy_flip_algorithm(Q,w)
     63        else:
     64            raise ValueError, "The optional argument algorithm can be either inductive or greedy"
     65        if Fs == []:
     66            raise ValueError, "The word %s does not contain a reduced expression for %s"%(Q,w.reduced_word())
     67        SimplicialComplex.__init__(self, vertex_set=Vs, maximal_faces=Fs, vertex_check=False, maximality_check=False)
     68        self.__custom_name = 'Subword complex'
     69        self._W = W
     70        self._cartan_type = W._type
     71        self._facets_dict = None
     72        if algorithm == "greedy":
     73            _facets_dict = {}
     74            for i in range(len(Fs)):
     75                X = self(Fs[i], facet_test=False)
     76                X._extended_root_conf_indices = Rs[i]
     77                _facets_dict[ tuple(sorted(Fs[i])) ] = X
     78            self._facets_dict = _facets_dict
     79        else:
     80            self._facets_dict = {}
     81
     82    def _repr_(self):
     83        r"""
     84        Returns a string representation of ``self``.
     85
     86        EXAMPLES::
     87
     88            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     89            sage: w = W.from_reduced_word([1,2,1])
     90            sage: SubwordComplex([2,1,2,1,2],w)
     91            Subword complex of type ['A', 2] for Q = [2, 1, 2, 1, 2] and pi = [1, 2, 1]
     92        """
     93        return 'Subword complex of type %s for Q = %s and pi = %s of genus %s'%(self.cartan_type(),self._Q,self._pi.reduced_word(),self._g)
     94
     95    def __cmp__(self,other):
     96        return self.word() == other.word() and self.pi() == other.pi()
     97
     98    def __call__(self, F, facet_test=True):
     99        F = tuple(F)
     100        if self._facets_dict is not None and self._facets_dict != dict() and F in self._facets_dict:
     101            return self._facets_dict[F]
     102        else:
     103            return SubwordComplexFacet(self, F, facet_test=facet_test)
     104
     105    def __contains__(self,F):
     106        r"""
     107        Tests if ``self`` contains a given iterable ``F``.
     108
     109        EXAMPLES::
     110       
     111            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     112            sage: w  = W.from_reduced_word([1,2,1])
     113            sage: S = SubwordComplex([2,1,2,1,2],w)
     114            sage: S.facets()
     115            [(0, 1), (0, 4), (1, 2), (2, 3), (3, 4)]
     116            sage: [0,1] in S
     117            True
     118            sage: [0,2] in S
     119            False
     120            sage: [0,1,5] in S
     121            False
     122            sage: [0] in S
     123            False
     124            sage: ['a','b'] in S
     125            False
     126        """
     127        W = self.group()
     128        Q = self.word()
     129        if not all( i in range(len(Q)) for i in F ):
     130            return False
     131        else:
     132            return W.from_word( Q[i] for i in range(len(Q)) if i not in F ) == self.pi()
     133
     134    def list(self):
     135        return [F for F in self]
     136
     137    # getting the stored properties
     138
     139    def group(self):
     140        r"""
     141        Returns the group associated to ``self``.
     142
     143        EXAMPLES::
     144       
     145            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     146            sage: w = W.from_reduced_word([1,2,1])
     147            sage: S = SubwordComplex([2,1,2,1,2],w)
     148            sage: S.group()
     149            Irreducible finite Coxeter group of rank 2 and type A2
     150        """
     151        return self._W
     152
     153    def cartan_type(self):
     154        r"""
     155        Returns the Cartan type of self.
     156
     157        EXAMPLES::
     158
     159            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     160            sage: w = W.from_reduced_word([1,2,1])
     161            sage: C = SubwordComplex([2,1,2,1,2],w)
     162            sage: C.cartan_type()
     163            ['A', 2]
     164        """
     165        from sage.combinat.root_system.cartan_type import CartanType
     166        if len(self._cartan_type) == 1:
     167            return CartanType([self._cartan_type[0]['series'],self._cartan_type[0]['rank']])
     168        else:
     169            return CartanType(self._cartan_type)
     170
     171    def word(self):
     172        r"""
     173        Returns the word in the simple generators associated to self.
     174
     175        EXAMPLES::
     176
     177            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     178            sage: w = W.from_reduced_word([1,2,1])
     179            sage: C = SubwordComplex([2,1,2,1,2],w)
     180            sage: C.word()
     181            [2, 1, 2, 1, 2]
     182        """
     183        return copy(self._Q)
     184
     185    def pi(self):
     186        r"""
     187        Returns the element in the Coxeter group associated to ``self``.
     188
     189        EXAMPLES::
     190
     191            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     192            sage: w = W.from_reduced_word([1,2,1])
     193            sage: C = SubwordComplex([2,1,2,1,2],w)
     194            sage: C.pi().reduced_word()
     195            [1, 2, 1]
     196        """
     197        return self._pi
     198
     199    def genus(self):
     200        return self._g
     201
     202    def facets(self):
     203        r"""
     204        Returns all facets of ``self``.
     205
     206        EXAMPLES::
     207
     208            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     209            sage: w = W.from_reduced_word([1,2,1])
     210            sage: S = SubwordComplex([2,1,2,1,2],w)
     211            sage: S.facets()
     212            [(0, 1), (0, 4), (1, 2), (2, 3), (3, 4)]
     213        """
     214        if self._facets_dict:
     215            return [ self._facets_dict[tuple(F)] for F in self._facets ]
     216        else:
     217            return [ self(F, facet_test=False) for F in self._facets ]
     218
     219    def __iter__(self):
     220        r"""
     221        Returns an iterator on the facets of ``self``.
     222
     223        EXAMPLES::
     224
     225            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     226            sage: w = W.from_reduced_word([1,2,1])
     227            sage: S = SubwordComplex([2,1,2,1,2],w)
     228            sage: for X in S:
     229            ...       print X
     230            (0, 1)
     231            (0, 4)
     232            (1, 2)
     233            (2, 3)
     234            (3, 4)
     235        """
     236        return iter(self.facets())
     237
     238    def greedy_facet(self,side="positive"):
     239        r"""
     240        Returns the negative (or positive) greedy facet of ``self``.
     241
     242        This is the lexicographically last (or first) facet of ``self``.
     243
     244        EXAMPLES::
     245
     246            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     247            sage: w = W.from_reduced_word([1,2,1])
     248            sage: S = SubwordComplex([2,1,2,1,2],w)
     249            sage: S.greedy_facet(side="positive")
     250            (0, 1)
     251            sage: S.greedy_facet(side="negative")
     252            (3, 4)
     253        """
     254        return SubwordComplexFacet(self,_greedy_facet(self.word(),self.pi(),g=self.genus(),side=side))
     255
     256    # topological properties
     257
     258    def is_sphere(self):
     259        """
     260        Returns True if the subword complex is a sphere.
     261
     262        EXAMPLES::
     263
     264            sage: W = CoxeterGroup(['A',3],index_set=[1,2,3])
     265            sage: w = W.from_reduced_word([2,3,2])
     266            sage: C = SubwordComplex([3,2,3,2,3],w)
     267            sage: C.is_sphere()
     268            True
     269
     270            sage: C = SubwordComplex([3,2,1,3,2,3],w)
     271            sage: C.is_sphere()
     272            False
     273        """
     274        W = self._pi.parent()
     275        w = W.demazure_product(self._Q)
     276        return w == self._pi
     277
     278    def is_ball(self):
     279        """
     280        Returns True if the subword complex is a ball. This is the case if and only if it is not a sphere.
     281
     282        EXAMPLES::
     283
     284            sage: W = CoxeterGroup(['A',3],index_set=[1,2,3])
     285            sage: w = W.from_reduced_word([2,3,2])
     286            sage: C = SubwordComplex([3,2,3,2,3],w)
     287            sage: C.is_ball()
     288            False
     289
     290            sage: C = SubwordComplex([3,2,1,3,2,3],w)
     291            sage: C.is_ball()
     292            True
     293        """
     294        return not self.is_sphere()
     295
     296    def is_pure(self):
     297        """
     298        Returns True since subword complexes are pure in general.
     299
     300        EXAMPLES::
     301
     302            sage: W = CoxeterGroup(['A',3],index_set=[1,2,3])
     303            sage: w = W.from_reduced_word([2,3,2])
     304            sage: C = SubwordComplex([3,2,3,2,3],w)
     305            sage: C.is_pure()
     306            True
     307        """
     308        return True
     309
     310    def dimension(self):
     311        """
     312        Returns the dimension of ``self``.
     313
     314        EXAMPLES::
     315
     316            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     317            sage: C = SubwordComplex([1,2,1,2,1],W.w0)
     318            sage: C.dimension()
     319            1
     320        """
     321        return self._facets[0].dimension()
     322
     323    @cached_method
     324    def is_root_independent(self):
     325        """
     326        Returns True if ``self`` is root independent. This is, if the root configuration of any (or equivalently all) facets is linearly independent.
     327
     328        EXAMPLES::
     329
     330            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     331            sage: C = SubwordComplex([1,2,1,2,1],W.w0)
     332            sage: C.is_root_independent()
     333            True
     334
     335            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     336            sage: C = SubwordComplex([1,2,1,2,1,2],W.w0)
     337            sage: C.is_root_independent()
     338            False
     339        """
     340        from sage.matrix.all import matrix
     341        M = matrix(self.greedy_facet(side="negative").root_configuration())
     342        return M.rank() == max( M.ncols(), M.nrows() )
     343
     344    @cached_method
     345    def is_double_root_free(self):
     346        """
     347        Returns True if ``self`` is double root free. This is, if the root configurations of all facets do not contain a root twice.
     348
     349        EXAMPLES::
     350
     351            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     352            sage: w = W.from_reduced_word([1,2,1])
     353            sage: C = SubwordComplex([1,2,1,2,1],w)
     354            sage: C.is_double_root_free()
     355            True
     356
     357            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     358            sage: w = W.from_reduced_word([1,2,1])
     359            sage: C = SubwordComplex([1,1,2,2,1,1],w)
     360            sage: C.is_double_root_free()
     361            True
     362           
     363            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     364            sage: w = W.from_reduced_word([1,2,1])
     365            sage: C = SubwordComplex([1,2,1,2,1,2],w)
     366            sage: C.is_double_root_free()
     367            False
     368        """
     369        if not self.is_root_independent():
     370            size = self.dimension() + 1
     371            for F in self:
     372                conf = F._root_configuration_indices()
     373                if len( set( conf ) ) < size:
     374                    return False
     375        return True
     376
     377    # root and weight properties
     378
     379    @cached_method
     380    def word_inversions(self):
     381        """
     382        FIX: New name???
     383        Returns the list of roots `w(\alpha_{q_i})` where `w` is the prefix of the ``self.word()``
     384        until position `i-1` applied on the `i`-th letter `q_i` of ``self.word()``.
     385
     386        EXAMPLES::
     387
     388            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     389            sage: S = SubwordComplex([1,2,1,2,1],W.w0)
     390            sage: S.word_inversions()
     391            [(1, 0), (1, 1), (0, 1), (-1, 0), (-1, -1)]
     392        """
     393        W = self._W
     394        Q = self._Q
     395        Phi = W.roots()
     396        pi = W.identity()
     397        roots = []
     398        for i in range(len(Q)):
     399            wi = Q[i]
     400            roots.append( Phi[(~pi)(W._index_set[wi]+1)-1] )
     401            pi = pi.apply_simple_reflection(wi)
     402        return roots
     403
     404    def kappa_preimages(self):
     405        """
     406        Returns a dictionary containing facets of ``self`` as keys,
     407        and list of elements of ``self.group()`` as values FIX: tba.
     408
     409        FIX: Add seealso
     410
     411        EXAMPLES::
     412
     413            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     414            sage: w = W.from_reduced_word([1,2,1])
     415            sage: S = SubwordComplex([1,2,1,2,1],w)
     416            sage: kappa_pres = S.kappa_preimages()
     417            sage: for F in S: print F, [ w.reduced_word() for w in kappa_pres[F] ]
     418            (0, 1) [[]]
     419            (0, 4) [[2, 1], [2]]
     420            (1, 2) [[1]]
     421            (2, 3) [[1, 2]]
     422            (3, 4) [[1, 2, 1]]
     423        """
     424        return dict( ( F, F.kappa_preimage() ) for F in self )
     425
     426    def brick_vectors(self,coefficients=None):
     427        """
     428        Returns the list of all brick_vectors of facets of ``self``.
     429
     430        FIX: Add seealso
     431
     432        EXAMPLES::
     433
     434            sage: tba
     435        """
     436        return [ F.brick_vector(coefficients=coefficients) for F in self ]
     437
     438    def minkowski_summand(self,i):
     439        """
     440        Returns the ``i``th Minkowski summand of ``self``.
     441
     442        EXAMPLES::
     443
     444            sage: tba
     445        """
     446        from sage.geometry.polyhedron.constructor import Polyhedron
     447        if not self.group().is_crystallographic():
     448            from sage.rings.all import CC,QQ
     449            print "Caution: the polytope is build with rational vertices."
     450            BV = [ [ QQ(CC(v).real()) for v in V ] for V in BV ]
     451        return Polyhedron([F.extended_weight_configuration()[i] for F in self])
     452
     453    def brick_polytope(self,coefficients=None):
     454        """
     455        Returns the `brick polytope of self.
     456        FIX: explain coefficients
     457
     458        EXAMPLES::
     459       
     460            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     461            sage: S = SubwordComplex([1,2,1,2,1],W.w0)
     462
     463            sage: X = S.brick_polytope(); X
     464            A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices
     465
     466            sage: Y = S.brick_polytope(coefficients=[1,2]); Y
     467            A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices
     468
     469            sage: X == Y
     470            False
     471        """
     472        from sage.geometry.polyhedron.constructor import Polyhedron
     473        BV = self.brick_vectors(coefficients=coefficients)
     474        if not self.group().is_crystallographic():
     475            from sage.rings.all import CC,QQ
     476            print "Caution: the polytope is build with rational vertices."
     477            BV = [ [ QQ(CC(v).real()) for v in V ] for V in BV ]
     478        return Polyhedron( BV )
     479
     480    def word_orbit_decomposition(self):
     481        """
     482        FIX: what was that???
     483        """
     484        Q = self.word()
     485        positions = range(len(Q))
     486        W = self.group()
     487        N = W.nr_reflections()
     488        w = W.w0
     489        I = W.index_set()
     490        I_decomp = set( [ frozenset( [ i, I[ w( W._index_set[i]+1 ) - 1 - N ] ] ) for i in I ] )
     491        return [ [ i for i in positions if Q[i] in I_part ] for I_part in I_decomp ]
     492
     493    def baricenter(self):
     494        """
     495        Returns the baricenter of the brick polytope of ``self``.
     496
     497        EXAMPLES::
     498
     499            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     500            sage: S = SubwordComplex([1,2,1,2,1],W.w0)
     501            sage: S.baricenter()
     502            (2/3, 4/3)
     503        """
     504        facets = self.facets()
     505        if not self.is_root_independent():
     506            facets = [ F for F in facets if F.is_vertex() ]
     507        return sum( F.brick_vector() for F in facets ) / len(facets)
     508
     509    # cambrian constructions
     510
     511    def cover_relations(self,label=False):
     512        N = self.group().nr_reflections()
     513        F = self.greedy_facet(side="positive")
     514        Fs = set([F])
     515        seen = set([F])
     516        covers = []
     517        while Fs != set():
     518            F = Fs.pop()
     519            seen.add(F)
     520            conf = F._extended_root_configuration_indices()
     521            for i in F:
     522                if conf[i] < N:
     523                    G = F.flip(i)
     524                    if label:
     525                        covers.append((F,G,i))
     526                    else:
     527                        covers.append((F,G))
     528                    if G not in seen:
     529                        Fs.add(G)
     530        return covers
     531
     532    def increasing_flip_graph(self,label=True):
     533        from sage.graphs.digraph import DiGraph
     534        return DiGraph( self.cover_relations(label=label) )
     535
     536    def interval(self,I,J):
     537        G = self.increasing_flip_graph()
     538        paths = G.all_paths(I,J)
     539        return set( K for path in paths for K in path )
     540
     541    def increasing_flip_poset(self):
     542        from sage.combinat.posets.posets import Poset
     543        cov = self.cover_relations()
     544        if not self.is_root_independent():
     545            Fs = [ F for F in self if F.is_vertex() ]
     546            cov = [ (a,b) for a,b in cov if a in Fs and b in Fs ]
     547        return Poset( ((),cov), facade=True )
     548
     549class SubwordComplexFacet(Simplex,Element):
     550    r"""
     551    A facet of a subword complex.
     552    """
     553   
     554    def __init__(self, parent, positions, facet_test=True):
     555        r"""
     556        Initializes a facet of the subword complex ``parent``.
     557
     558        EXAMPLES::
     559
     560            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     561            sage: S = SubwordComplex([1,2,1,2,1],W.w0)
     562            sage: F = S([1,2]); F
     563            (1, 2)
     564        """
     565        if facet_test and positions not in parent:
     566            raise ValueError, "The given iterable %s is not a facet of the subword complex %s"%(positions,parent)
     567        Element.__init__(self, parent)
     568        Simplex.__init__(self, sorted(positions))
     569        self._extended_root_conf_indices = None
     570        self._extended_weight_conf = None
     571
     572    def __eq__(self,other):
     573        return self.tuple() == other.tuple()
     574
     575    def _extended_root_configuration_indices(self):
     576        r"""
     577        Returns the indices of the roots in ``self.group().roots()`` of the extended root configuration.
     578
     579        EXAMPLES::
     580
     581            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     582            sage: w = W.from_reduced_word([1,2,1])
     583            sage: S = SubwordComplex([2,1,2,1,2],w)
     584            sage: F = S([1,2]); F
     585            (1, 2)
     586            sage: F._extended_root_configuration_indices()
     587            [1, 2, 4, 2, 0]
     588        """
     589        if self._extended_root_conf_indices is None:
     590            self._extended_root_conf_indices = _extended_root_configuration_indices(self.parent().group(), self.parent().word(), self)
     591        return self._extended_root_conf_indices
     592
     593    def _root_configuration_indices(self):
     594        r"""
     595        Returns the indices of the roots in ``self.group().roots()`` of the root configuration.
     596
     597        EXAMPLES::
     598
     599            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     600            sage: w = W.from_reduced_word([1,2,1])
     601            sage: S = SubwordComplex([2,1,2,1,2],w)
     602            sage: F = S([1,2]); F
     603            (1, 2)
     604            sage: F._root_configuration_indices()
     605            [2, 4]
     606        """
     607        indices = self._extended_root_configuration_indices()
     608        return [ indices[i] for i in self ]
     609
     610    def extended_root_configuration(self):
     611        r"""
     612        Returns the extended root configuration.
     613
     614        EXAMPLES::
     615
     616            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     617            sage: w = W.from_reduced_word([1,2,1])
     618            sage: S = SubwordComplex([2,1,2,1,2],w)
     619            sage: F = S([1,2]); F
     620            (1, 2)
     621            sage: F.extended_root_configuration()
     622            [(0, 1), (1, 1), (0, -1), (1, 1), (1, 0)]
     623        """
     624        Phi = self.parent().group().roots()
     625        return [ Phi[i] for i in self._extended_root_configuration_indices() ]
     626
     627    def root_configuration(self):
     628        r"""
     629        Returns the root configuration.
     630
     631        EXAMPLES::
     632
     633            sage: W = CoxeterGroup(['A',2],index_set=[1,2])
     634            sage: w = W.from_reduced_word([1,2,1])
     635            sage: S = SubwordComplex([2,1,2,1,2],w)
     636            sage: F = S([1,2]); F
     637            (1, 2)
     638            sage: F.root_configuration()
     639            [(1, 1), (0, -1)]
     640        """
     641        Phi = self.parent().group().roots()
     642        return [ Phi[i] for i in self._root_configuration_indices() ]
     643
     644    def upper_root_configuration(self):
     645        conf = self._root_configuration_indices()
     646        W = self.parent().group()
     647        Phi = W.roots()
     648        N = len(Phi)/2
     649        return [ Phi[i-N] for i in conf if i >= N ]
     650
     651    def extended_weight_configuration(self,coefficients=None):
     652        """
     653        Returns the multiset
     654
     655        EXAMPLES::
     656
     657            tba
     658        """
     659        if coefficients is not None or self._extended_weight_conf is None:
     660            W = self.parent().group()
     661            I = W.index_set()
     662            Lambda = W.fundamental_weights()
     663            if coefficients is not None:
     664                Lambda = [ coefficients[i] * Lambda[i] for i in range(len(Lambda)) ]
     665            Q = self.parent().word()
     666
     667            Phi = W.roots()
     668
     669            V_weights = []
     670
     671            pi = W.identity()
     672            for i in range(len(Q)):
     673                wi = Q[i]
     674                fund_weight = Lambda[W._index_set[wi]]
     675                V_weights.append( sum(fund_weight[j]*Phi[ (~pi)(j+1)-1 ] for j in range(len(fund_weight)) ) )
     676                if i not in self:
     677                    pi = pi.apply_simple_reflection(wi)
     678            if coefficients is None:
     679                self._extended_weight_conf = V_weights
     680            return V_weights
     681        else:
     682            return self._extended_weight_conf
     683
     684    def weight_configuration(self):
     685        extended_configuration = self.extended_weight_configuration()
     686        return [ extended_configuration[i] for i in self ]
     687
     688    def brick_vector(self,coefficients = None):
     689        weight_conf = self.extended_weight_configuration(coefficients=coefficients)
     690        return sum(weight for weight in weight_conf)
     691
     692    def rays_of_root_fan(self):
     693        ext_root_conf = self.extended_root_configuration()
     694        root_conf = self.root_configuration()
     695        M = Matrix( root_conf )
     696        M_inverse = M.inverse()
     697        rays = []
     698        for j in range(len(ext_root_conf)):
     699            root = ext_root_conf[j]
     700            if j in self:
     701#                rays.append(-root)
     702                rays.append(-root*M_inverse)
     703            else:
     704                new_root = root*M_inverse
     705#                rays.append( vector([( 1 if i < j else -1 ) * new_root[self.tuple().index(i)] for i in self ])*M )
     706                rays.append( vector([( 1 if i < j else -1 ) * new_root[self.tuple().index(i)] for i in self ]) )
     707        return rays
     708
     709    def root_fan(self):
     710        from sage.geometry.fan import Fan
     711        root_conf = self.root_configuration()
     712        return Fan( self.parent().facets(), self.rays_of_root_fan() )
     713
     714    def root_polytope(self, orbits):
     715        from sage.geometry.polyhedron.constructor import Polyhedron
     716        M_inverse = Matrix( self.root_configuration() ).inverse()
     717        rays = self.rays_of_root_fan()
     718#        rhocheck = sum( rays[i] for i in range(len(rays)) if i not in self )*M_inverse
     719        rhocheck = sum( rays[i] for i in range(len(rays)) if i not in self )
     720#        print rhocheck
     721        inequalities = []
     722#        orbits = self.parent().word_orbit_decomposition()
     723        for x in range(len(rays)):
     724            for orbit in orbits:
     725                if x in orbit:
     726                    i = min([j for j in self if j in orbit])
     727                    c = rhocheck[self.tuple().index(i)]
     728                    inequalities.append( [c] + list(rays[x]) )
     729        return Polyhedron(ieqs=inequalities)
     730
     731    def kappa_preimage(self):
     732        W = self.parent().group()
     733        N = W.nr_reflections()
     734        root_conf = self._root_configuration_indices()
     735        return [ w for w in W if all( w(i+1) <= N for i in root_conf ) ]
     736
     737    def product_of_upper_roots(self):
     738        W = self.parent().group()
     739        return W.prod( W.root_to_reflection(beta) for beta in self.upper_root_configuration() )
     740
     741    @cached_method
     742    def local_cone(self):
     743        return Cone(self.root_configuration())
     744
     745    def is_vertex(self):
     746        S = self.parent()
     747        if not S.is_root_independent():
     748            W = S.group()
     749            N = W.nr_reflections()
     750            root_conf = self._root_configuration_indices()
     751            for w in W:
     752                if all( w(i+1) <= N for i in root_conf ):
     753                    return False
     754        return True
     755
     756    def flip(self,i,return_position=False):
     757        F = set([j for j in self])
     758        R = [j for j in self._extended_root_configuration_indices()]
     759        j = _flip(self.parent().group(), F, R, i)
     760        new_facet = SubwordComplexFacet(self.parent(), F)
     761        new_facet._extended_root_conf_indices = tuple(R)
     762        if return_position:
     763            return new_facet, j
     764        else:
     765            return new_facet
     766
     767    def plot(self, compact=False, list_colors=[], thickness=3, roots=True, labels=[], fontsize=14, shift=(0,0), **args):
     768        if any( x is not 'A' for x in self.parent().group().series() ):
     769            raise ValueError, "Plotting is currently only implemented in type A"
     770        from sage.plot.line import line
     771        from sage.plot.text import text
     772        from sage.plot.colors import colors
     773        from sage.combinat.permutation import Permutation
     774        x = 1
     775        W = self.parent().group()
     776        Q = self.parent().word()
     777        n = W.rank()
     778        permutation = Permutation(range(1,n+2))
     779        pseudolines = [[(shift[0]+0,shift[1]+i),.5] for i in range(n+1)]
     780        x_max = .5
     781        contact_points = []
     782        root_labels = []
     783        pseudoline_labels = []
     784        if labels is not False:
     785            pseudoline_labels += [(pseudoline, (shift[0]-.1, shift[1]+pseudoline), "center") for pseudoline in range(n+1)]
     786        if roots:
     787            extended_root_conf = self.extended_root_configuration()
     788        for position in range(len(Q)):
     789            y = W._index_set[Q[position]]
     790            pseudoline1 = permutation(y+1)-1
     791            pseudoline2 = permutation(y+2)-1
     792            if compact:
     793                x = max(pseudolines[pseudoline1].pop(), pseudolines[pseudoline2].pop())
     794                x_max = max(x+1, x_max)
     795            else:
     796                pseudolines[pseudoline1].pop()
     797                pseudolines[pseudoline2].pop()
     798                x = x_max
     799                x_max += 1
     800            if position in self:
     801                pseudolines[pseudoline1] += [(shift[0]+x+1,shift[1]+y), x+1]
     802                pseudolines[pseudoline2] += [(shift[0]+x+1,shift[1]+y+1), x+1]
     803                contact_points += [[(shift[0]+x+.5,shift[1]+y), (shift[0]+x+.5, shift[1]+y+1)]]               
     804            else:
     805                pseudolines[pseudoline1] += [(shift[0]+x+.6,shift[1]+y), (shift[0]+x+.6,shift[1]+y+1), x+1]
     806                pseudolines[pseudoline2] += [(shift[0]+x+.5,shift[1]+y+1), (shift[0]+x+.5,shift[1]+y), x+1]
     807                permutation = permutation._left_to_right_multiply_on_left(Permutation((y+1, y+2)))
     808            if roots:
     809                root_labels.append((extended_root_conf[position],(shift[0]+x+.35,shift[1]+y+.5)))
     810            if labels is not False:
     811                pseudoline_labels += [(pseudoline1, (shift[0]+x+.35,shift[1]+y+.05), "bottom"), (pseudoline2, (shift[0]+x+.35,shift[1]+y+.95), "top")]
     812        list_colors += ['red', 'blue', 'green', 'orange', 'yellow', 'purple'] + colors.keys()
     813        thickness = max(thickness, 2)
     814        L = line([(1,1)])
     815        for contact_point in contact_points:
     816            L += line(contact_point, rgbcolor=[0,0,0], thickness=thickness-1)
     817        for pseudoline in range(n+1):
     818            pseudolines[pseudoline].pop()
     819            pseudolines[pseudoline].append((shift[0]+x_max, shift[1]+permutation.inverse()(pseudoline+1)-1))
     820            L += line(pseudolines[pseudoline], color=list_colors[pseudoline], thickness=thickness)
     821        for root_label in root_labels:
     822            L += text(root_label[0], root_label[1], rgbcolor=[0,0,0], fontsize=fontsize, vertical_alignment="center", horizontal_alignment="right")
     823        if len(labels) < n+1:
     824            labels = range(1,n+2)
     825        for pseudoline_label in pseudoline_labels:
     826            L += text(labels[pseudoline_label[0]], pseudoline_label[1], color=list_colors[pseudoline_label[0]], fontsize=fontsize, vertical_alignment=pseudoline_label[2], horizontal_alignment="right")
     827        if labels is not False:
     828            for pseudoline in range(n+1):
     829                L += text(labels[pseudoline], (shift[0]+x_max+.1, shift[1]+permutation.inverse()(pseudoline+1)-1), color=list_colors[pseudoline], fontsize=fontsize, vertical_alignment="center", horizontal_alignment="left")
     830        return L
     831
     832    def show(self, *kwds, **args):
     833        return self.plot().show(axes=False,*kwds,**args)
     834
     835    def show(self, **kwds):
     836        """
     837       
     838        EXAMPLES::
     839       
     840        """
     841        self.plot(**kwds).show(axes=False)
     842
     843def _construct_facets(Q, w, g=0, n=None, pos=0, l=None):
     844    r"""
     845    Returns the list of facets of the subword complex associated to the word Q and the element w in a Coxeter group W.
     846    """
     847    if n is None:
     848        n = len(Q)
     849    if l is None:
     850        first = True
     851        l = w.length()
     852    else:
     853        first = False
     854
     855    if l == 0 and g == 0:
     856        return [range(pos,n)]
     857    elif n < l+pos+2*g:
     858        return []
     859
     860    s = Q[pos]
     861    X = _construct_facets(Q,w,g=g,n=n,pos=pos+1,l=l)
     862    for f in X:
     863        f.append(pos)
     864
     865    if w.has_left_descent(s):
     866        Y = _construct_facets(Q,w.apply_simple_reflection_left(s),g=g,  n=n,pos=pos+1,l=l-1)
     867        Y = X+Y
     868    elif g > 0:
     869        Y = _construct_facets(Q,w.apply_simple_reflection_left(s),g=g-1,n=n,pos=pos+1,l=l+1)
     870        Y = X+Y
     871    else:
     872        Y = X
     873    if first:
     874        return sorted([ sorted(x) for x in Y ])
     875    else:
     876        return Y
     877
     878def _greedy_facet(Q,w,g=0,side="negative",n=None, pos=0,l=None,elems=[]):
     879    r"""
     880    Returns the (positive or negative) *greedy facet* of ``self``.
     881    """
     882    if side == "negative":
     883        pass
     884    elif side == "positive":
     885        Q.reverse()
     886        w = w.inverse()
     887    else:
     888        raise ValueError, "The optional argument side is not positive or negative"
     889
     890    if n is None:
     891        n = len(Q)
     892    if l is None:
     893        l = w.length()
     894
     895    if l == 0 and g == 0:
     896        return elems + range(pos,n)
     897    elif n < l+pos+2*g:
     898        return []
     899
     900    s = Q[pos]
     901
     902    if w.has_left_descent(s):
     903        X = _greedy_facet(Q,w.apply_simple_reflection_left(s),g=g,n=n,pos=pos+1,l=l-1,elems=elems)
     904    elif g > 0:
     905        X = _greedy_facet(Q,w.apply_simple_reflection_left(s),g=g-1,n=n,pos=pos+1,l=l+1,elems=elems)
     906    if X == []:
     907        X = _greedy_facet(Q,w,g=g,n=n,pos=pos+1,l=l,elems=elems+[pos])
     908       
     909
     910    if side == "positive":
     911        X = [ n - 1 - i for i in X ]
     912        Q.reverse()
     913        w = w.inverse()
     914
     915    return set(X)
     916
     917def _extended_root_configuration_indices(W, Q, F):
     918        V_roots = []
     919        pi = W.identity()
     920        for i in range(len(Q)):
     921            wi = Q[i]
     922            V_roots.append((~pi)(W._index_set[wi]+1)-1)
     923            if i not in F:
     924                pi = pi.apply_simple_reflection(wi)
     925        return V_roots
     926
     927def _flip(W, positions, extended_root_conf_indices, i, side="both"):
     928    r = extended_root_conf_indices[i]
     929    nr_ref = W.nr_reflections()
     930    r_minus = (r + nr_ref) % (2*nr_ref)
     931    positions.remove(i)
     932    j = i
     933    for k in range(len(extended_root_conf_indices)):
     934        if j == i and k < i and k not in positions and extended_root_conf_indices[k] == r_minus and side != "positive":
     935            j = k
     936        elif j == i and k > i and k not in positions and extended_root_conf_indices[k] == r and side != "negative":
     937            j = k
     938    positions.add(j)
     939    if j != i:
     940        t = W.reflections()[ min(r,r_minus) ]
     941        for k in range(min(i,j)+1,max(i,j)+1):
     942            extended_root_conf_indices[k] = t( extended_root_conf_indices[k]+1 ) - 1
     943    return j
     944
     945def _greedy_flip_algorithm(Q, w):
     946    W = w.parent()
     947    F = _greedy_facet(Q,w,side="positive")
     948    R = _extended_root_configuration_indices(W, Q, F)
     949    facet_list = [F]
     950    extended_root_conf_indices_list = [R]
     951    flip_to_ancestors = [-1]
     952    next_index = 0
     953    while flip_to_ancestors != []:
     954        has_new_child = False
     955        for i in sorted(F):
     956            if (not has_new_child) and (i >= next_index):
     957                j = _flip(W, F, R, i, side="positive")
     958                if j != i:
     959                    flip_to_ancestors.append(j)
     960                    next_index = i+1
     961                    has_new_child = True                   
     962                    facet_list.append([x for x in F])
     963                    extended_root_conf_indices_list.append([x for x in R])
     964        if not has_new_child:
     965            i = flip_to_ancestors.pop()
     966            if i != -1:
     967                j = _flip(W, F, R, i, side="negative")
     968                next_index = j+1
     969    return facet_list, extended_root_conf_indices_list