def __classcall_private__(cls, starting_weight, cartan_type=None, starting_weight_parent=None): """ Classcall to mend the input. Internally, the :class:`~sage.combinat.crystals.littlemann_path.CrystalOfLSPaths` code works with a ``starting_weight`` that is in the weight space associated to the crystal. The user can, however, also input a ``cartan_type`` and the coefficients of the fundamental weights as ``starting_weight``. This code transforms the input into the right format (also necessary for UniqueRepresentation). TESTS:: sage: crystals.LSPaths(['A',2,1],[-1,0,1]) The crystal of LS paths of type ['A', 2, 1] and weight -Lambda[0] + Lambda[2] sage: R = RootSystem(['B',2,1]) sage: La = R.weight_space(extended=True).basis() sage: C = crystals.LSPaths(['B',2,1],[0,0,1]) sage: B = crystals.LSPaths(La[2]) sage: B is C True """ if cartan_type is not None: cartan_type, starting_weight = CartanType( starting_weight), cartan_type if cartan_type.is_affine(): extended = True else: extended = False R = RootSystem(cartan_type) P = R.weight_space(extended=extended) Lambda = P.basis() offset = R.index_set()[Integer(0)] starting_weight = P.sum(starting_weight[j - offset] * Lambda[j] for j in R.index_set()) if starting_weight_parent is None: starting_weight_parent = starting_weight.parent() else: # Both the weight and the parent of the weight are passed as arguments of init to be able # to distinguish between crystals with the extended and non-extended weight lattice! if starting_weight.parent() != starting_weight_parent: raise ValueError( "The passed parent is not equal to parent of the inputted weight!" ) return super(CrystalOfLSPaths, cls).__classcall__( cls, starting_weight, starting_weight_parent=starting_weight_parent)
def __classcall_private__(cls, starting_weight, cartan_type = None, starting_weight_parent = None): """ Classcall to mend the input. Internally, the :class:`~sage.combinat.crystals.littlemann_path.CrystalOfLSPaths` code works with a ``starting_weight`` that is in the weight space associated to the crystal. The user can, however, also input a ``cartan_type`` and the coefficients of the fundamental weights as ``starting_weight``. This code transforms the input into the right format (also necessary for UniqueRepresentation). TESTS:: sage: crystals.LSPaths(['A',2,1],[-1,0,1]) The crystal of LS paths of type ['A', 2, 1] and weight -Lambda[0] + Lambda[2] sage: R = RootSystem(['B',2,1]) sage: La = R.weight_space(extended=True).basis() sage: C = crystals.LSPaths(['B',2,1],[0,0,1]) sage: B = crystals.LSPaths(La[2]) sage: B is C True """ if cartan_type is not None: cartan_type, starting_weight = CartanType(starting_weight), cartan_type if cartan_type.is_affine(): extended = True else: extended = False R = RootSystem(cartan_type) P = R.weight_space(extended = extended) Lambda = P.basis() offset = R.index_set()[Integer(0)] starting_weight = P.sum(starting_weight[j-offset]*Lambda[j] for j in R.index_set()) if starting_weight_parent is None: starting_weight_parent = starting_weight.parent() else: # Both the weight and the parent of the weight are passed as arguments of init to be able # to distinguish between crystals with the extended and non-extended weight lattice! if starting_weight.parent() != starting_weight_parent: raise ValueError("The passed parent is not equal to parent of the inputted weight!") return super(CrystalOfLSPaths, cls).__classcall__(cls, starting_weight, starting_weight_parent = starting_weight_parent)
def __classcall_private__(cls, starting_weight, cartan_type=None): """ Classcall to mend the input. Internally, the CrystalOfLSPaths code works with a ``starting_weight`` that is in the ``weight_space`` associated to the crystal. The user can, however, also input a ``cartan_type`` and the coefficients of the fundamental weights as ``starting_weight``. This code transforms the input into the right format (also necessary for UniqueRepresentation). TESTS:: sage: CrystalOfLSPaths(['A',2,1],[-1,0,1]) The crystal of LS paths of type ['A', 2, 1] and weight -Lambda[0] + Lambda[2] sage: R = RootSystem(['B',2,1]) sage: La = R.weight_space().basis() sage: C = CrystalOfLSPaths(['B',2,1],[0,0,1]) sage: B = CrystalOfLSPaths(La[2]) sage: B is C True """ if cartan_type is not None: cartan_type, starting_weight = CartanType( starting_weight), cartan_type if cartan_type.is_affine(): extended = True else: extended = False R = RootSystem(cartan_type) P = R.weight_space(extended=extended) Lambda = P.basis() offset = R.index_set()[Integer(0)] starting_weight = P.sum(starting_weight[j - offset] * Lambda[j] for j in R.index_set()) return super(CrystalOfLSPaths, cls).__classcall__(cls, starting_weight)
def __classcall_private__(cls, starting_weight, cartan_type = None): """ Classcall to mend the input. Internally, the CrystalOfLSPaths code works with a ``starting_weight`` that is in the ``weight_space`` associated to the crystal. The user can, however, also input a ``cartan_type`` and the coefficients of the fundamental weights as ``starting_weight``. This code transforms the input into the right format (also necessary for UniqueRepresentation). TESTS:: sage: CrystalOfLSPaths(['A',2,1],[-1,0,1]) The crystal of LS paths of type ['A', 2, 1] and weight -Lambda[0] + Lambda[2] sage: R = RootSystem(['B',2,1]) sage: La = R.weight_space().basis() sage: C = CrystalOfLSPaths(['B',2,1],[0,0,1]) sage: B = CrystalOfLSPaths(La[2]) sage: B is C True """ if cartan_type is not None: cartan_type, starting_weight = CartanType(starting_weight), cartan_type if cartan_type.is_affine(): extended = True else: extended = False R = RootSystem(cartan_type) P = R.weight_space(extended = extended) Lambda = P.basis() offset = R.index_set()[Integer(0)] starting_weight = P.sum(starting_weight[j-offset]*Lambda[j] for j in R.index_set()) return super(CrystalOfLSPaths, cls).__classcall__(cls, starting_weight)
class CrystalOfLSPaths(UniqueRepresentation, Parent): r""" Crystal graph of LS paths generated from the straight-line path to a given weight. INPUT: - ``cartan_type`` -- the Cartan type of a finite or affine root system - ``starting_weight`` -- a weight given as a list of coefficients of the fundamental weights The crystal class of piecewise linear paths in the weight space, generated from a straight-line path from the origin to a given element of the weight lattice. OUTPUT: - a tuple of weights defining the directions of the piecewise linear segments EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1]); C The crystal of LS paths of type ['A', 2, 1] and weight (-1, 0, 1) sage: c = C.module_generators[0]; c (-Lambda[0] + Lambda[2],) sage: [c.f(i) for i in C.index_set()] [None, None, (Lambda[1] - Lambda[2],)] sage: R = C.R; R Root system of type ['A', 2, 1] sage: Lambda = R.weight_space().basis(); Lambda Finite family {0: Lambda[0], 1: Lambda[1], 2: Lambda[2]} sage: b=C(tuple([-Lambda[0]+Lambda[2]])) sage: b==c True sage: b.f(2) (Lambda[1] - Lambda[2],) For classical highest weight crystals we can also compare the results with the tableaux implementation:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: list(set(C.list())) [(-Lambda[1] - Lambda[2],), (-Lambda[1] + 1/2*Lambda[2], Lambda[1] - 1/2*Lambda[2]), (-Lambda[1] + 2*Lambda[2],), (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]), (Lambda[1] - 2*Lambda[2],), (-2*Lambda[1] + Lambda[2],), (2*Lambda[1] - Lambda[2],), (Lambda[1] + Lambda[2],)] sage: C.cardinality() 8 sage: B = CrystalOfTableaux(['A',2],shape=[2,1]) sage: B.cardinality() 8 sage: B.digraph().is_isomorphic(C.digraph()) True TESTS:: sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1]) sage: TestSuite(C).run(skip=['_test_elements', '_test_elements_eq', '_test_enumerated_set_contains', '_test_some_elements']) sage: C = CrystalOfLSPaths(['E',6],[1,0,0,0,0,0]) sage: TestSuite(C).run() REFERENCES:: .. [L] P. Littelmann, Paths and root operators in representation theory. Ann. of Math. (2) 142 (1995), no. 3, 499-525. """ @staticmethod def __classcall__(cls, cartan_type, starting_weight): """ cartan_type and starting_weight are lists, which are mutable. The class UniqueRepresentation requires immutable inputs. The following code fixes this problem. TESTS:: sage: CrystalOfLSPaths.__classcall__(CrystalOfLSPaths,['A',2,1],[-1,0,1]) The crystal of LS paths of type ['A', 2, 1] and weight (-1, 0, 1) """ cartan_type = CartanType(cartan_type) starting_weight = tuple(starting_weight) return super(CrystalOfLSPaths, cls).__classcall__(cls, cartan_type, starting_weight) def __init__(self, cartan_type, starting_weight): """ EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1]); C The crystal of LS paths of type ['A', 2, 1] and weight (-1, 0, 1) sage: C.R Root system of type ['A', 2, 1] sage: C.weight -Lambda[0] + Lambda[2] sage: C.weight.parent() Extended weight space over the Rational Field of the Root system of type ['A', 2, 1] sage: C.module_generators [(-Lambda[0] + Lambda[2],)] """ self._cartan_type = CartanType(cartan_type) self.R = RootSystem(cartan_type) self._name = "The crystal of LS paths of type %s and weight %s"%(cartan_type,starting_weight) if self._cartan_type.is_affine(): self.extended = True if all(i>=0 for i in starting_weight): Parent.__init__(self, category = HighestWeightCrystals()) else: Parent.__init__(self, category = Crystals()) else: self.extended = False Parent.__init__(self, category = FiniteCrystals()) Lambda = self.R.weight_space(extended = self.extended).basis() offset = self.R.index_set()[Integer(0)] zero_weight = self.R.weight_space(extended = self.extended).zero() self.weight = sum([zero_weight]+[starting_weight[j-offset]*Lambda[j] for j in self.R.index_set()]) if self.weight == zero_weight: initial_element = self(tuple([])) else: initial_element = self(tuple([self.weight])) self.module_generators = [initial_element] def _repr_(self): """ EXAMPLES:: sage: CrystalOfLSPaths(['B',3],[1,1,0]) # indirect doctest The crystal of LS paths of type ['B', 3] and weight (1, 1, 0) """ return self._name class Element(ElementWrapper): """ TESTS:: sage: C = CrystalOfLSPaths(['E',6],[1,0,0,0,0,0]) sage: c=C.an_element() sage: TestSuite(c).run() """ def endpoint(self): r""" Computes the endpoint of the path. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: b = C.module_generators[0] sage: b.endpoint() Lambda[1] + Lambda[2] sage: b.f_string([1,2,2,1]) (-Lambda[1] - Lambda[2],) sage: b.f_string([1,2,2,1]).endpoint() -Lambda[1] - Lambda[2] sage: b.f_string([1,2]) (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]) sage: b.f_string([1,2]).endpoint() 0 sage: b = C([]) sage: b.endpoint() 0 """ if len(self.value) > 0: return sum(self.value) return self.parent().R.weight_space(extended = self.parent().extended).zero() def compress(self): r""" Merges consecutive positively parallel steps present in the path. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: Lambda = C.R.weight_space().fundamental_weights(); Lambda Finite family {1: Lambda[1], 2: Lambda[2]} sage: c = C(tuple([1/2*Lambda[1]+1/2*Lambda[2], 1/2*Lambda[1]+1/2*Lambda[2]])) sage: c.compress() (Lambda[1] + Lambda[2],) """ def positively_parallel_weights(v, w): """ Checks whether the vectors ``v`` and ``w`` are positive scalar multiples of each other. """ supp = v.support() if len(supp) > 0: i = supp[0] if v[i]*w[i] > 0 and v[i]*w == w[i]*v: return True return False if len(self.value) == 0: return self q = [] curr = self.value[0] for i in range(1,len(self.value)): if positively_parallel_weights(curr,self.value[i]): curr = curr + self.value[i] else: q.append(curr) curr = self.value[i] q.append(curr) return self.parent()(tuple(q)) def split_step(self, which_step, r): r""" Splits indicated step into two parallel steps of relative lengths `r` and `1-r`. INPUT: - ``which_step`` -- a position in the tuple ``self`` - ``r`` -- a rational number between 0 and 1 EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: b = C.module_generators[0] sage: b.split_step(0,1/3) (1/3*Lambda[1] + 1/3*Lambda[2], 2/3*Lambda[1] + 2/3*Lambda[2]) """ assert which_step in range(len(self.value)) v = self.value[which_step] return self.parent()(self.value[:which_step]+tuple([r*v,(1-r)*v])+self.value[which_step+1:]) def reflect_step(self, which_step, i): r""" Apply the `i`-th simple reflection to the indicated step in ``self``. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: b = C.module_generators[0] sage: b.reflect_step(0,1) (-Lambda[1] + 2*Lambda[2],) sage: b.reflect_step(0,2) (2*Lambda[1] - Lambda[2],) """ assert i in self.index_set() assert which_step in range(len(self.value)) return self.parent()(self.value[:which_step]+tuple([self.value[which_step].simple_reflection(i)])+self.value[which_step+1:]) def _string_data(self, i): r""" Computes the `i`-string data of ``self``. TESTS:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: b = C.module_generators[0] sage: b._string_data(1) () sage: b._string_data(2) () sage: b.f(1)._string_data(1) ((0, -1, -1),) sage: b.f(1).f(2)._string_data(2) ((0, -1, -1),) """ if len(self.value) == 0: return () # get the i-th simple coroot alv = self.value[0].parent().alphacheck()[i] # Compute the i-heights of the steps of vs steps = [v.scalar(alv) for v in self.value] # Get the wet step data minima_pos = [] ps = 0 psmin = 0 for ix in range(len(steps)): ps = ps + steps[ix] if ps < psmin: minima_pos.append((ix,ps,steps[ix])) psmin = ps return tuple(minima_pos) def epsilon(self, i): r""" Returns the distance to the beginning of the `i`-string. This method overrides the generic implementation in the category of crystals since this computation is more efficient. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: [c.epsilon(1) for c in C] [0, 1, 0, 0, 1, 0, 1, 2] sage: [c.epsilon(2) for c in C] [0, 0, 1, 2, 1, 1, 0, 0] """ return self.e(i,length_only=True) def phi(self, i): r""" Returns the distance to the end of the `i`-string. This method overrides the generic implementation in the category of crystals since this computation is more efficient. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: [c.phi(1) for c in C] [1, 0, 0, 1, 0, 2, 1, 0] sage: [c.phi(2) for c in C] [1, 2, 1, 0, 0, 0, 0, 1] """ return self.f(i,length_only=True) def e(self, i, power=1, to_string_end=False, length_only=False): r""" Returns the `i`-th crystal raising operator on ``self``. INPUT: - ``i`` -- element of the index set of the underlying root system - ``power`` -- positive integer; specifies the power of the raising operator to be applied (default: 1) - ``to_string_end`` -- boolean; if set to True, returns the dominant end of the `i`-string of ``self``. (default: False) - ``length_only`` -- boolean; if set to True, returns the distance to the dominant end of the `i`-string of ``self``. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: c = C[2]; c (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]) sage: c.e(1) sage: c.e(2) (-Lambda[1] + 2*Lambda[2],) sage: c.e(2,to_string_end=True) (-Lambda[1] + 2*Lambda[2],) sage: c.e(1,to_string_end=True) (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]) sage: c.e(1,length_only=True) 0 """ assert i in self.index_set() data = self._string_data(i) # compute the minimum i-height M on the path if len(data) == 0: M = 0 else: M = data[-1][1] max_raisings = floor(-M) if length_only: return max_raisings # set the power of e_i to apply if to_string_end: p = max_raisings else: p = power if p > max_raisings: return None # copy the vector sequence into a working vector sequence ws #!!! ws only needs to be the actual vector sequence, not some #!!! fancy crystal graph element ws = self.parent()(self.value) ix = len(data)-1 while ix >= 0 and data[ix][1] < M + p: # get the index of the current step to be processed j = data[ix][0] # find the i-height where the current step might need to be split if ix == 0: prev_ht = M + p else: prev_ht = min(data[ix-1][1],M+p) # if necessary split the step. Then reflect the wet part. if data[ix][1] - data[ix][2] > prev_ht: ws = ws.split_step(j,1-(prev_ht-data[ix][1])/(-data[ix][2])) ws = ws.reflect_step(j+1,i) else: ws = ws.reflect_step(j,i) ix = ix - 1 #!!! at this point we should return the fancy crystal graph element #!!! corresponding to the humble vector sequence ws return self.parent()(ws.compress()) def dualize(self): r""" Returns dualized path. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: for c in C: ... print c, c.dualize() ... (Lambda[1] + Lambda[2],) (-Lambda[1] - Lambda[2],) (-Lambda[1] + 2*Lambda[2],) (Lambda[1] - 2*Lambda[2],) (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]) (1/2*Lambda[1] - Lambda[2], -1/2*Lambda[1] + Lambda[2]) (Lambda[1] - 2*Lambda[2],) (-Lambda[1] + 2*Lambda[2],) (-Lambda[1] - Lambda[2],) (Lambda[1] + Lambda[2],) (2*Lambda[1] - Lambda[2],) (-2*Lambda[1] + Lambda[2],) (-Lambda[1] + 1/2*Lambda[2], Lambda[1] - 1/2*Lambda[2]) (-Lambda[1] + 1/2*Lambda[2], Lambda[1] - 1/2*Lambda[2]) (-2*Lambda[1] + Lambda[2],) (2*Lambda[1] - Lambda[2],) """ if len(self.value) == 0: return self dual_path = [-v for v in self.value] dual_path.reverse() return self.parent()(tuple(dual_path)) def f(self, i, power=1, to_string_end=False, length_only=False): r""" Returns the `i`-th crystal lowering operator on ``self``. INPUT: - ``i`` -- element of the index set of the underlying root system - ``power`` -- positive integer; specifies the power of the lowering operator to be applied (default: 1) - ``to_string_end`` -- boolean; if set to True, returns the anti-dominant end of the `i`-string of ``self``. (default: False) - ``length_only`` -- boolean; if set to True, returns the distance to the anti-dominant end of the `i`-string of ``self``. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: c = C.module_generators[0] sage: c.f(1) (-Lambda[1] + 2*Lambda[2],) sage: c.f(1,power=2) sage: c.f(2) (2*Lambda[1] - Lambda[2],) sage: c.f(2,to_string_end=True) (2*Lambda[1] - Lambda[2],) sage: c.f(2,length_only=True) 1 sage: C = CrystalOfLSPaths(['A',2,1],[-1,-1,2]) sage: c = C.module_generators[0] sage: c.f(2,power=2) (Lambda[0] + Lambda[1] - 2*Lambda[2],) """ dual_path = self.dualize() dual_path = dual_path.e(i, power, to_string_end, length_only) if length_only: return dual_path if dual_path == None: return None return dual_path.dualize() def s(self, i): r""" Computes the reflection of ``self`` along the `i`-string. This method is more efficient than the generic implementation since it uses powers of `e` and `f` in the Littelmann model directly. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: c = C.module_generators[0] sage: c.s(1) (-Lambda[1] + 2*Lambda[2],) sage: c.s(2) (2*Lambda[1] - Lambda[2],) sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1]) sage: c = C.module_generators[0]; c (-Lambda[0] + Lambda[2],) sage: c.s(2) (Lambda[1] - Lambda[2],) sage: c.s(1) (-Lambda[0] + Lambda[2],) sage: c.f(2).s(1) (Lambda[0] - Lambda[1],) """ ph = self.phi(i) ep = self.epsilon(i) diff = ph - ep if diff >= 0: return self.f(i, power=diff) else: return self.e(i, power=-diff) def _latex_(self): r""" Latex method for ``self``. EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2],[1,1]) sage: c = C.module_generators[0] sage: c._latex_() [\Lambda_{1} + \Lambda_{2}] """ return [latex(p) for p in self.value]
class ClassicalCrystalOfAlcovePaths(UniqueRepresentation, Parent): r""" Implementation of crystal of alcove paths of the given classical type with given highest weight, based on the Lenart--Postnikov model [LP2008]. These are highest weight crystals for classical types `A_n`, `B_n`, `C_n`, `D_n` and the exceptional types `F_4`, `G_2`, `E_6`, `E_7`, `E_8`. INPUT: - ``cartan_type`` is the Cartan type of a classical Dynkin diagram - ``highest_weight`` is a dominant weight as a list of coefficients of the fundamental weights `Lambda_i` In this model, a chain of roots is associated to the given highest_weight, and then the elements of the crystal are indexed by "admissible subsets" which indicate "folding positions" along the chain of roots. See [LP2008] for details. TODO: - Resolve speed issues; `E_6(\Lambda_1)` takes just under 4 minutes to list(). To construct the highest-weight node takes 15 sec for `E_6(\Lambda_4)`. The initial chain has 42 roots. TESTS: The following example appears in Figure 2 of [LP2008]:: sage: C = ClassicalCrystalOfAlcovePaths(['G',2],[0,1]); sage: G = C.digraph() sage: GG= DiGraph({ ... () : {(0) : 2 }, ... (0) : {(0,8) : 1 }, ... (0,1) : {(0,1,7) : 2 }, ... (0,1,2) : {(0,1,2,9) : 1 }, ... (0,1,2,3) : {(0,1,2,3,4) : 2 }, ... (0,1,2,6) : {(0,1,2,3) : 1 }, ... (0,1,2,9) : {(0,1,2,6) : 1 }, ... (0,1,7) : {(0,1,2) : 2 }, ... (0,1,7,9) : {(0,1,2,9) : 2 }, ... (0,5) : {(0,1) : 1, (0,5,7) : 2 }, ... (0,5,7) : {(0,5,7,9) : 1 }, ... (0,5,7,9) : {(0,1,7,9) : 1 }, ... (0,8) : {(0,5) : 1 }, ... }) sage: G.is_isomorphic(GG) True sage: for edge in sorted([(u.value, v.value, i) for (u,v,i) in G.edges()]): ... print edge ([], [0], 2) ([0], [0, 8], 1) ([0, 1], [0, 1, 7], 2) ([0, 1, 2], [0, 1, 2, 9], 1) ([0, 1, 2, 3], [0, 1, 2, 3, 4], 2) ([0, 1, 2, 6], [0, 1, 2, 3], 1) ([0, 1, 2, 9], [0, 1, 2, 6], 1) ([0, 1, 7], [0, 1, 2], 2) ([0, 1, 7, 9], [0, 1, 2, 9], 2) ([0, 5], [0, 1], 1) ([0, 5], [0, 5, 7], 2) ([0, 5, 7], [0, 5, 7, 9], 1) ([0, 5, 7, 9], [0, 1, 7, 9], 1) ([0, 8], [0, 5], 1) REFERENCES: .. [LP2008] C. Lenart and A. Postnikov. A combinatorial model for crystals of Kac-Moody algebras. Trans. Amer. Math. Soc. 360 (2008), 4349-4381. """ @staticmethod def __classcall__(cls, cartan_type, highest_weight): """ cartan_type and heighest_weight are lists, which are mutable, this causes a problem for class UniqueRepresentation, the following code fixes this problem. EXAMPLES:: sage: ClassicalCrystalOfAlcovePaths.__classcall__(ClassicalCrystalOfAlcovePaths,['A',3],[0,1,0]) <class 'sage.combinat.crystals.alcove_path.ClassicalCrystalOfAlcovePaths_with_category'> """ cartan_type = CartanType(cartan_type) highest_weight = tuple(highest_weight) return super(ClassicalCrystalOfAlcovePaths, cls).__classcall__(cls, cartan_type, highest_weight) def __init__(self, cartan_type, highest_weight): """ EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[1,0,0]) sage: C.list() [[], [0], [0, 1], [0, 1, 2]] sage: TestSuite(C).run() """ Parent.__init__(self, category = ClassicalCrystals()) self._cartan_type = CartanType(cartan_type) self._name = "The crystal of alcove paths for type %s"%cartan_type self.chain_cache = {} self.endweight_cache = {} self.R = RootSystem(cartan_type) alpha = self.R.root_space().simple_roots() Lambda = self.R.weight_space().basis() self.positive_roots = sorted(self.R.root_space().positive_roots()); self.weight = Lambda[Integer(1)] - Lambda[Integer(1)] offset = self.R.index_set()[Integer(0)] for j in self.R.index_set(): self.weight = self.weight + highest_weight[j-offset]*Lambda[j] self.initial_element = self([]) self.initial_element.chain = self.get_initial_chain(self.weight) rho = (Integer(1)/Integer(2))*sum(self.positive_roots) self.initial_element.endweight = rho self.chain_cache[ str([]) ] = self.initial_element.chain self.endweight_cache[ str([]) ] = self.initial_element.endweight self.module_generators = [self.initial_element] self._list = super(ClassicalCrystalOfAlcovePaths, self).list() self._digraph = super(ClassicalCrystalOfAlcovePaths, self).digraph() self._digraph_closure = self.digraph().transitive_closure() def get_initial_chain(self, highest_weight): """ Called internally by __init__() to construct the chain of roots associated to the highest weight element. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.get_initial_chain(RootSystem(['A',3]).weight_space().basis()[1]) [[alpha[1], alpha[1]], [alpha[1] + alpha[2], alpha[1] + alpha[2]], [alpha[1] + alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[3]]] """ pos_roots = self.positive_roots tis = self.R.index_set() tis.reverse() cv_to_pos_root = {} to_sort = [] for r in pos_roots: j = highest_weight.scalar( r.associated_coroot() ) if (int(math.floor(j)) - j == Integer(0)): j = j - Integer(1) j = int(math.floor(j)) for k in (ellipsis_range(Integer(0),Ellipsis,j)): cv = [] cv.append((Integer(1)/highest_weight.scalar(r.associated_coroot()))*k) for i in tis: cv.append((Integer(1)/highest_weight.scalar(r.associated_coroot()))*r.associated_coroot().coefficient(i)) cv_to_pos_root[ str(cv) ] = r to_sort.append( cv ) to_sort.sort() # Note: Python sorts nested lists lexicographically by default. lambda_chain = [] for t in to_sort: lambda_chain.append( [ cv_to_pos_root[str(t)], cv_to_pos_root[str(t)] ] ) return lambda_chain def _element_constructor_(self, value): """ Coerces value into self. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[1,0,0]) sage: C.module_generators [[]] sage: C([]).e(1) sage: C([]).f(1) [0] """ return self.element_class(self, value) def list(self): """ Returns a list of the elements of self. .. warning:: This can be slow! EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.list() [[], [0], [0, 1], [0, 2], [0, 1, 2], [0, 1, 2, 3]] """ return self._list def digraph(self): """ Returns the directed graph associated to self. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.digraph().vertices() [[], [0], [0, 1], [0, 2], [0, 1, 2], [0, 1, 2, 3]] sage: C.digraph().edges() [([], [0], 2), ([0], [0, 1], 1), ([0], [0, 2], 3), ([0, 1], [0, 1, 2], 3), ([0, 2], [0, 1, 2], 1), ([0, 1, 2], [0, 1, 2, 3], 2)] """ return self._digraph def lt_elements(self, x, y): r""" Returns True if and only if there is a path from x to y in the crystal graph. Because the crystal graph is classical, it is a directed acyclic graph which can be interpreted as a poset. This function implements the comparison function of this poset. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: x = C([]) sage: y = C([0,2]) sage: C.lt_elements(x,y) True sage: C.lt_elements(y,x) False sage: C.lt_elements(x,x) False """ assert x.parent() == self and y.parent() == self if self._digraph_closure.has_edge(x,y): return True return False
class ClassicalCrystalOfAlcovePaths(UniqueRepresentation, Parent): r""" Implementation of crystal of alcove paths of the given classical type with given highest weight, based on the Lenart--Postnikov model [LP2008]. These are highest weight crystals for classical types `A_n`, `B_n`, `C_n`, `D_n` and the exceptional types `F_4`, `G_2`, `E_6`, `E_7`, `E_8`. INPUT: - ``cartan_type`` is the Cartan type of a classical Dynkin diagram - ``highest_weight`` is a dominant weight as a list of coefficients of the fundamental weights `Lambda_i` In this model, a chain of roots is associated to the given highest_weight, and then the elements of the crystal are indexed by "admissible subsets" which indicate "folding positions" along the chain of roots. See [LP2008] for details. TODO: - Resolve speed issues; `E_6(\Lambda_1)` takes just under 4 minutes to list(). To construct the highest-weight node takes 15 sec for `E_6(\Lambda_4)`. The initial chain has 42 roots. TESTS: The following example appears in Figure 2 of [LP2008]:: sage: C = ClassicalCrystalOfAlcovePaths(['G',2],[0,1]); sage: G = C.digraph() sage: GG= DiGraph({ ... () : {(0) : 2 }, ... (0) : {(0,8) : 1 }, ... (0,1) : {(0,1,7) : 2 }, ... (0,1,2) : {(0,1,2,9) : 1 }, ... (0,1,2,3) : {(0,1,2,3,4) : 2 }, ... (0,1,2,6) : {(0,1,2,3) : 1 }, ... (0,1,2,9) : {(0,1,2,6) : 1 }, ... (0,1,7) : {(0,1,2) : 2 }, ... (0,1,7,9) : {(0,1,2,9) : 2 }, ... (0,5) : {(0,1) : 1, (0,5,7) : 2 }, ... (0,5,7) : {(0,5,7,9) : 1 }, ... (0,5,7,9) : {(0,1,7,9) : 1 }, ... (0,8) : {(0,5) : 1 }, ... }) sage: G.is_isomorphic(GG) True sage: for edge in sorted([(u.value, v.value, i) for (u,v,i) in G.edges()]): ... print edge ([], [0], 2) ([0], [0, 8], 1) ([0, 1], [0, 1, 7], 2) ([0, 1, 2], [0, 1, 2, 9], 1) ([0, 1, 2, 3], [0, 1, 2, 3, 4], 2) ([0, 1, 2, 6], [0, 1, 2, 3], 1) ([0, 1, 2, 9], [0, 1, 2, 6], 1) ([0, 1, 7], [0, 1, 2], 2) ([0, 1, 7, 9], [0, 1, 2, 9], 2) ([0, 5], [0, 1], 1) ([0, 5], [0, 5, 7], 2) ([0, 5, 7], [0, 5, 7, 9], 1) ([0, 5, 7, 9], [0, 1, 7, 9], 1) ([0, 8], [0, 5], 1) REFERENCES: .. [LP2008] C. Lenart and A. Postnikov. A combinatorial model for crystals of Kac-Moody algebras. Trans. Amer. Math. Soc. 360 (2008), 4349-4381. """ @staticmethod def __classcall__(cls, cartan_type, highest_weight): """ cartan_type and heighest_weight are lists, which are mutable, this causes a problem for class UniqueRepresentation, the following code fixes this problem. EXAMPLES:: sage: ClassicalCrystalOfAlcovePaths.__classcall__(ClassicalCrystalOfAlcovePaths,['A',3],[0,1,0]) <class 'sage.combinat.crystals.alcove_path.ClassicalCrystalOfAlcovePaths_with_category'> """ cartan_type = CartanType(cartan_type) highest_weight = tuple(highest_weight) return super(ClassicalCrystalOfAlcovePaths, cls).__classcall__(cls, cartan_type, highest_weight) def __init__(self, cartan_type, highest_weight): """ EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[1,0,0]) sage: C.list() [[], [0], [0, 1], [0, 1, 2]] sage: TestSuite(C).run() """ Parent.__init__(self, category=ClassicalCrystals()) self._cartan_type = CartanType(cartan_type) self._name = "The crystal of alcove paths for type %s" % cartan_type self.chain_cache = {} self.endweight_cache = {} self.R = RootSystem(cartan_type) alpha = self.R.root_space().simple_roots() Lambda = self.R.weight_space().basis() self.positive_roots = sorted(self.R.root_space().positive_roots()) self.weight = Lambda[Integer(1)] - Lambda[Integer(1)] offset = self.R.index_set()[Integer(0)] for j in self.R.index_set(): self.weight = self.weight + highest_weight[j - offset] * Lambda[j] self.initial_element = self([]) self.initial_element.chain = self.get_initial_chain(self.weight) rho = (Integer(1) / Integer(2)) * sum(self.positive_roots) self.initial_element.endweight = rho self.chain_cache[str([])] = self.initial_element.chain self.endweight_cache[str([])] = self.initial_element.endweight self.module_generators = [self.initial_element] self._list = super(ClassicalCrystalOfAlcovePaths, self).list() self._digraph = super(ClassicalCrystalOfAlcovePaths, self).digraph() self._digraph_closure = self.digraph().transitive_closure() def get_initial_chain(self, highest_weight): """ Called internally by __init__() to construct the chain of roots associated to the highest weight element. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.get_initial_chain(RootSystem(['A',3]).weight_space().basis()[1]) [[alpha[1], alpha[1]], [alpha[1] + alpha[2], alpha[1] + alpha[2]], [alpha[1] + alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[3]]] """ pos_roots = self.positive_roots tis = self.R.index_set() tis.reverse() cv_to_pos_root = {} to_sort = [] for r in pos_roots: j = highest_weight.scalar(r.associated_coroot()) if (int(math.floor(j)) - j == Integer(0)): j = j - Integer(1) j = int(math.floor(j)) for k in (ellipsis_range(Integer(0), Ellipsis, j)): cv = [] cv.append( (Integer(1) / highest_weight.scalar(r.associated_coroot())) * k) for i in tis: cv.append((Integer(1) / highest_weight.scalar(r.associated_coroot())) * r.associated_coroot().coefficient(i)) cv_to_pos_root[str(cv)] = r to_sort.append(cv) to_sort.sort( ) # Note: Python sorts nested lists lexicographically by default. lambda_chain = [] for t in to_sort: lambda_chain.append( [cv_to_pos_root[str(t)], cv_to_pos_root[str(t)]]) return lambda_chain def _element_constructor_(self, value): """ Coerces value into self. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[1,0,0]) sage: C.module_generators [[]] sage: C([]).e(1) sage: C([]).f(1) [0] """ return self.element_class(self, value) def list(self): """ Returns a list of the elements of self. .. warning:: This can be slow! EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.list() [[], [0], [0, 1], [0, 2], [0, 1, 2], [0, 1, 2, 3]] """ return self._list def digraph(self): """ Returns the directed graph associated to self. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: C.digraph().vertices() [[], [0], [0, 1], [0, 2], [0, 1, 2], [0, 1, 2, 3]] sage: C.digraph().edges() [([], [0], 2), ([0], [0, 1], 1), ([0], [0, 2], 3), ([0, 1], [0, 1, 2], 3), ([0, 2], [0, 1, 2], 1), ([0, 1, 2], [0, 1, 2, 3], 2)] """ return self._digraph def lt_elements(self, x, y): r""" Returns True if and only if there is a path from x to y in the crystal graph. Because the crystal graph is classical, it is a directed acyclic graph which can be interpreted as a poset. This function implements the comparison function of this poset. EXAMPLES:: sage: C = ClassicalCrystalOfAlcovePaths(['A',3],[0,1,0]) sage: x = C([]) sage: y = C([0,2]) sage: C.lt_elements(x,y) True sage: C.lt_elements(y,x) False sage: C.lt_elements(x,x) False """ assert x.parent() == self and y.parent() == self if self._digraph_closure.has_edge(x, y): return True return False