From 538310ecee8396846d920d89b1d5f764462078d1 Mon Sep 17 00:00:00 2001 From: Nomail Name Date: Sun, 3 Feb 2013 09:16:17 -0800 Subject: [PATCH] Trac #14052: Implementation and drawing of infinite crystals up to a maximum depths --- src/sage/categories/crystals.py | 179 ++++++++++++++++-- .../categories/highest_weight_crystals.py | 29 +++ src/sage/combinat/backtrack.py | 17 +- src/sage/combinat/crystals/crystals.py | 63 +++--- .../crystals/highest_weight_crystals.py | 14 +- src/sage/combinat/crystals/littelmann_path.py | 6 +- 6 files changed, 256 insertions(+), 52 deletions(-) diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 45fa432c593..c57230272d4 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -188,10 +188,17 @@ def Lambda(self): """ return self.weight_lattice_realization().fundamental_weights() - def __iter__(self): + def __iter__(self, index_set=None, max_depth=float('inf')): """ Returns the iterator of ``self``. + INPUT: + + - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + then use the index set of the crystal + + - ``max_depth`` -- (Default: infinity) The maximum depth to build + EXAMPLES:: sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1]) @@ -208,15 +215,104 @@ def __iter__(self): (Lambda[1] - Lambda[2],) sage: g.next() (Lambda[0] - Lambda[1],) + sage: h = C.__iter__(index_set=[1,2]) + sage: h.next() + (-Lambda[0] + Lambda[2],) + sage: h.next() + (Lambda[1] - Lambda[2],) + sage: h.next() + (Lambda[0] - Lambda[1],) + sage: h.next() + Traceback (most recent call last): + ... + StopIteration + sage: g = C.__iter__(max_depth=1) + sage: g.next() + (-Lambda[0] + Lambda[2],) + sage: g.next() + (Lambda[1] - Lambda[2],) + sage: g.next() + (Lambda[0] - Lambda[1] + delta,) + sage: h.next() + Traceback (most recent call last): + ... + StopIteration + """ + if index_set is None: + index_set = self.index_set() + if max_depth < float('inf'): + from sage.combinat.backtrack import TransitiveIdealGraded + return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set] + + [x.e(i) for i in index_set], + self.module_generators, max_depth).__iter__() from sage.combinat.backtrack import TransitiveIdeal - return TransitiveIdeal(lambda x: [x.f(i) for i in self.index_set()] - + [x.e(i) for i in self.index_set()], self.module_generators).__iter__() + return TransitiveIdeal(lambda x: [x.f(i) for i in index_set] + + [x.e(i) for i in index_set], + self.module_generators).__iter__() + + def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"), + direction="both"): + r""" + Construct the subcrystal from ``generators`` using `e_i` and `f_i` + for all `i` in ``index_set``. + + INPUT: + + - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + then use the index set of the crystal + + - ``generators`` -- (Default: ``None``) The list of generators; if + ``None`` then use the module generators of the crystal + + - ``max_depth`` -- (Default: infinity) The maximum depth to build + - ``direction`` -- (Default: ``'both'``) The direction to build + the subcrystal. It can be one of the following: + + - ``'both'`` - Using both `e_i` and `f_i` + - ``'upper'`` - Using `e_i` + - ``'lower'`` - Using `f_i` + + EXAMPLES:: + + sage: C = KirillovReshetikhinCrystal(['A',3,1], 1, 2) + sage: S = list(C.subcrystal(index_set=[1,2])); S + [[[1, 1]], [[1, 2]], [[1, 3]], [[2, 2]], [[2, 3]], [[3, 3]]] + sage: C.cardinality() + 10 + sage: len(S) + 6 + sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)])) + [[[1, 4]], [[2, 4]], [[1, 3]], [[2, 3]]] + sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], max_depth=1)) + [[[1, 4]], [[2, 4]], [[1, 3]]] + sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='upper')) + [[[1, 4]], [[1, 3]]] + sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='lower')) + [[[1, 4]], [[2, 4]]] + """ + if index_set is None: + index_set = self.index_set() + if generators is None: + generators = self.module_generators + from sage.combinat.backtrack import TransitiveIdealGraded + + if direction == 'both': + return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set] + + [x.e(i) for i in index_set], + generators, max_depth) + if direction == 'upper': + return TransitiveIdealGraded(lambda x: [x.e(i) for i in index_set], + generators, max_depth) + if direction == 'lower': + return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set], + generators, max_depth) + raise ValueError("direction must be either 'both', 'upper', or 'lower'") def crystal_morphism(self, g, index_set = None, automorphism = lambda i : i, direction = 'down', direction_image = 'down', similarity_factor = None, similarity_factor_domain = None, cached = False, acyclic = True): - """ + r""" Constructs a morphism from the crystal ``self`` to another crystal. The input `g` can either be a function of a (sub)set of elements of self to element in another crystal or a dictionary between certain elements. @@ -359,9 +455,16 @@ def morphism(b): else: return CachedFunction(morphism) - def digraph(self): + def digraph(self, subset=None, index_set=None): """ - Returns the DiGraph associated to self. + Returns the DiGraph associated to ``self``. + + INPUT: + + - ``subset`` -- (Optional) A subset of vertices for + which the digraph should be constructed + + - ``index_set`` -- (Optional) The index set to draw arrows EXAMPLES:: @@ -390,21 +493,72 @@ def digraph(self): sage: C.cartan_type()._index_set_coloring[4]="purple" sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz + Here is an example of how to take the top part up to a given depth of an infinite dimensional + crystal:: + + sage: C = CartanType(['C',2,1]) + sage: La = C.root_system().weight_lattice().fundamental_weights() + sage: T = HighestWeightCrystal(La[0]) + sage: S = [b for b in T.__iter__(max_depth=3)] + sage: G = T.digraph(subset=S); G + Digraph on 5 vertices + sage: G.vertices() + [(1/2*Lambda[0] + Lambda[1] - Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), + (-Lambda[0] + 2*Lambda[1] - delta,), (Lambda[0] - 2*Lambda[1] + 2*Lambda[2] - delta,), + (1/2*Lambda[0] - Lambda[1] + Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (Lambda[0],)] + + Here is a way to construct a picture of a Demazure crystal using + the ``subset`` option:: + + sage: B = CrystalOfTableaux(['A',2], shape=[2,1]) + sage: C = CombinatorialFreeModule(QQ,B) + sage: t = B.highest_weight_vector() + sage: b = C(t) + sage: D = B.demazure_operator(b,[2,1]); D + B[[[1, 1], [3]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 3], [3]]] + B[[[1, 1], [2]]] + sage: G = B.digraph(subset=D.support()) + sage: G.vertices() + [[[1, 1], [2]], [[1, 2], [2]], [[1, 3], [2]], [[1, 1], [3]], [[1, 3], [3]]] + sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz + + We can also choose to display particular arrows using the + ``index_set`` option:: + + sage: C = KirillovReshetikhinCrystal(['D',4,1], 2, 1) + sage: G = C.digraph(index_set=[1,3]) + sage: len(G.edges()) + 20 + sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz + TODO: add more tests """ from sage.graphs.all import DiGraph + from sage.categories.highest_weight_crystals import HighestWeightCrystals d = {} - for x in self: + if self in HighestWeightCrystals: + f = lambda (u,v,label): ({}) + else: + f = lambda (u,v,label): ({"backward":label ==0}) + + # Parse optional arguments + if subset is None: + subset = self + if index_set is None: + index_set = self.index_set() + + for x in subset: d[x] = {} - for i in self.index_set(): + for i in index_set: child = x.f(i) - if child is None: + if child is None or child not in subset: continue d[x][child]=i G = DiGraph(d) if have_dot2tex(): - G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = self.cartan_type()._index_set_coloring, - edge_options = lambda (u,v,label): ({"backward":label ==0})) + G.set_latex_options(format="dot2tex", + edge_labels = True, + color_by_label = self.cartan_type()._index_set_coloring, + edge_options = f) return G def latex_file(self, filename): @@ -460,8 +614,7 @@ def _latex_(self, **options): print "dot2tex not available. Install after running \'sage -sh\'" return G=self.digraph() - G.set_latex_options(format="dot2tex", edge_labels = True, - edge_options = lambda (u,v,label): ({"backward":label ==0}), **options) + G.set_latex_options(**options) return G._latex_() latex = _latex_ diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 448da2a99a0..f506f7899ae 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -152,6 +152,35 @@ def lowest_weight_vectors(self): """ return [g for g in self if g.is_lowest_weight()] + def __iter__(self, index_set=None, max_depth = float("inf")): + """ + Returns the iterator of ``self``. + + INPUT: + + - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + then use the index set of the crystal + + - ``max_depth`` -- (Default: infinity) The maximum depth to build + + EXAMPLES:: + + sage: C = CrystalOfLSPaths(['A',2,1],[0,1,0]) + sage: [p for p in C.__iter__(max_depth=3)] + [(Lambda[1],), (Lambda[0] - Lambda[1] + Lambda[2],), (2*Lambda[0] - Lambda[2],), + (-Lambda[0] + 2*Lambda[2] - delta,), + (1/2*Lambda[0] + Lambda[1] - Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[2] - 1/2*delta), + (-Lambda[0] + Lambda[1] + 1/2*Lambda[2] - delta, Lambda[0] - 1/2*Lambda[2])] + sage: [p for p in C.__iter__(index_set=[0, 1], max_depth=3)] + [(Lambda[1],), (Lambda[0] - Lambda[1] + Lambda[2],), (-Lambda[0] + 2*Lambda[2] - delta,)] + """ + if index_set is None: + index_set = self.index_set() + from sage.combinat.backtrack import TransitiveIdealGraded + return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set], + self.module_generators, max_depth).__iter__() + class ElementMethods: pass + diff --git a/src/sage/combinat/backtrack.py b/src/sage/combinat/backtrack.py index 656fe4f08d5..a1204b24bbb 100644 --- a/src/sage/combinat/backtrack.py +++ b/src/sage/combinat/backtrack.py @@ -846,8 +846,12 @@ class TransitiveIdealGraded(TransitiveIdeal): INPUT: - ``relation``: a function (or callable) returning a list (or iterable) + - ``generators``: a list (or iterable) + - ``max_depth`` -- (Default: infinity) Specifies the maximal depth to + which elements are computed + Returns the set `S` of elements that can be obtained by repeated application of ``relation`` on the elements of ``generators``. @@ -898,7 +902,7 @@ class TransitiveIdealGraded(TransitiveIdeal): [[3, 1, 2, 4], [2, 1, 3, 4], [2, 1, 4, 3], [3, 2, 1, 4], [2, 3, 1, 4], [3, 1, 4, 2], [2, 3, 4, 1], [3, 4, 1, 2], [3, 2, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [4, 3, 1, 2], [4, 2, 1, 3], [3, 4, 2, 1], [4, 2, 3, 1], [4, 3, 2, 1]] """ - def __init__(self, succ, generators): + def __init__(self, succ, generators, max_depth=float("inf")): r""" TESTS:: @@ -911,10 +915,11 @@ def __init__(self, succ, generators): """ self._succ = succ self._generators = generators + self._max_depth = max_depth def __iter__(self): r""" - Returns an iterator on the elements of self. + Returns an iterator on the elements of ``self``. TESTS:: @@ -929,10 +934,14 @@ def __iter__(self): sage: C = TransitiveIdeal(lambda x: [], (1,2)) sage: list(C) # indirect doctest [1, 2] + + sage: [i for i in TransitiveIdealGraded(lambda i: [i+1] if i<10 else [], [0], max_depth=1).__iter__()] + [0, 1] """ current_level = self._generators known = set(current_level) - while len(current_level) > 0: + depth = 0 + while len(current_level) > 0 and depth <= self._max_depth: next_level = set() for x in current_level: yield x @@ -942,4 +951,6 @@ def __iter__(self): next_level.add(y) known.add(y) current_level = next_level + depth += 1 return + diff --git a/src/sage/combinat/crystals/crystals.py b/src/sage/combinat/crystals/crystals.py index 4a4db4f05a5..f186be649a6 100644 --- a/src/sage/combinat/crystals/crystals.py +++ b/src/sage/combinat/crystals/crystals.py @@ -30,12 +30,12 @@ This crystal actually models a representation of a Lie algebra if -it satisfies some further local conditions due to Stembridge [St2003]. +it satisfies some further local conditions due to Stembridge [St2003]_. REFERENCES: - .. [St2003] J. Stembridge, *A local characterization of simply-laced crystals*, - Trans. Amer. Math. Soc. 355 (2003), no. 12, 4807-4823. +.. [St2003] J. Stembridge, *A local characterization of simply-laced crystals*, + Trans. Amer. Math. Soc. 355 (2003), no. 12, 4807-4823. EXAMPLES: @@ -96,27 +96,18 @@ :class:`FiniteCrystals`, :class:`HighestWeightCrystals`. -Caveat: this crystal library, although relatively featureful for -classical crystals, is still in an early development stage, and the -syntax details may be subject to changes. +.. TODO:: -TODO: + - Vocabulary and conventions: -- Vocabulary and conventions: + - For a classical crystal: connected / highest weight / + irreducible - - For a classical crystal: connected / highest weight / - irreducible + - ... - - ... + - Layout instructions for plot() for rank 2 types -- More introductory doc explaining the mathematical background - -- Layout instructions for plot() for rank 2 types - -- Littelmann paths and/or alcove paths (this would give us the - exceptional types) - -- RestrictionOfCrystal + - RestrictionOfCrystal Most of the above features (except Littelmann/alcove paths) are in @@ -153,24 +144,24 @@ from sage.combinat.backtrack import GenericBacktracker class CrystalBacktracker(GenericBacktracker): - def __init__(self, crystal): - """ - Time complexity: `O(nf)` amortized for each produced - element, where `n` is the size of the index set, and f is + def __init__(self, crystal, index_set=None): + r""" + Time complexity: `O(nF)` amortized for each produced + element, where `n` is the size of the index set, and `F` is the cost of computing `e` and `f` operators. - Memory complexity: O(depth of the crystal) + Memory complexity: `O(D)` where `D` is the depth of the crystal. Principle of the algorithm: - Let C be a classical crystal. It's an acyclic graph where all + Let `C` be a classical crystal. It's an acyclic graph where all connected component has a unique element without predecessors (the highest weight element for this component). Let's assume for - simplicity that C is irreducible (i.e. connected) with highest - weight element u. + simplicity that `C` is irreducible (i.e. connected) with highest + weight element `u`. One can define a natural spanning tree of `C` by taking - `u` as rot of the tree, and for any other element + `u` as the root of the tree, and for any other element `y` taking as ancestor the element `x` such that there is an `i`-arrow from `x` to `y` with `i` minimal. Then, a path from `u` to `y` @@ -182,7 +173,7 @@ def __init__(self, crystal): search walk through this spanning tree. In practice, this can be achieved recursively as follow: take an element `x`, and consider in turn each successor `y = f_i(x)`, ignoring - those such that `y = f_j(x')` for some `x'` and + those such that `y = f_j(x^{\prime})` for some `x^{\prime}` and `j=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())