Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/categories/crystals.py
8817 views
1
r"""
2
Crystals
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2010 Anne Schilling <anne at math.ucdavis.edu>
6
#
7
# Distributed under the terms of the GNU General Public License (GPL)
8
# http://www.gnu.org/licenses/
9
#******************************************************************************
10
11
from sage.misc.cachefunc import CachedFunction, cached_method
12
from sage.misc.abstract_method import abstract_method
13
from sage.categories.category_singleton import Category_singleton
14
from sage.categories.enumerated_sets import EnumeratedSets
15
from sage.misc.latex import latex
16
from sage.combinat import ranker
17
from sage.graphs.dot2tex_utils import have_dot2tex
18
from sage.rings.integer import Integer
19
20
class Crystals(Category_singleton):
21
r"""
22
The category of crystals
23
24
See :mod:`sage.combinat.crystals` for an introduction to crystals.
25
26
EXAMPLES::
27
28
sage: C = Crystals()
29
sage: C
30
Category of crystals
31
sage: C.super_categories()
32
[Category of... enumerated sets]
33
sage: C.example()
34
Highest weight crystal of type A_3 of highest weight omega_1
35
36
Parents in this category should implement the following methods:
37
38
- either an attribute ``_cartan_type`` or a method ``cartan_type``
39
40
- ``module_generators``: a list (or container) of distinct elements
41
which generate the crystal using `f_i`
42
43
Furthermore, their elements should implement the following methods:
44
45
- ``x.e(i)`` (returning `e_i(x)`)
46
47
- ``x.f(i)`` (returning `f_i(x)`)
48
49
- ``x.epsilon(i)`` (returning `\varepsilon_i(x)`)
50
51
- ``x.phi(i)`` (returning `\varphi_i(x)`)
52
53
EXAMPLES::
54
55
sage: from sage.misc.abstract_method import abstract_methods_of_class
56
sage: abstract_methods_of_class(Crystals().element_class)
57
{'required': ['e', 'epsilon', 'f', 'phi', 'weight'], 'optional': []}
58
59
TESTS::
60
61
sage: TestSuite(C).run()
62
sage: B = Crystals().example()
63
sage: TestSuite(B).run(verbose = True)
64
running ._test_an_element() . . . pass
65
running ._test_category() . . . pass
66
running ._test_elements() . . .
67
Running the test suite of self.an_element()
68
running ._test_category() . . . pass
69
running ._test_eq() . . . pass
70
running ._test_not_implemented_methods() . . . pass
71
running ._test_pickling() . . . pass
72
running ._test_stembridge_local_axioms() . . . pass
73
pass
74
running ._test_elements_eq_reflexive() . . . pass
75
running ._test_elements_eq_symmetric() . . . pass
76
running ._test_elements_eq_transitive() . . . pass
77
running ._test_elements_neq() . . . pass
78
running ._test_enumerated_set_contains() . . . pass
79
running ._test_enumerated_set_iter_cardinality() . . . pass
80
running ._test_enumerated_set_iter_list() . . . pass
81
running ._test_eq() . . . pass
82
running ._test_fast_iter() . . . pass
83
running ._test_not_implemented_methods() . . . pass
84
running ._test_pickling() . . . pass
85
running ._test_some_elements() . . . pass
86
running ._test_stembridge_local_axioms() . . . pass
87
"""
88
89
def super_categories(self):
90
r"""
91
EXAMPLES::
92
93
sage: Crystals().super_categories()
94
[Category of enumerated sets]
95
"""
96
return [EnumeratedSets()]
97
98
def example(self, choice="highwt", **kwds):
99
r"""
100
Returns an example of a crystal, as per
101
:meth:`Category.example()
102
<sage.categories.category.Category.example>`.
103
104
INPUT::
105
106
- ``choice`` -- str [default: 'highwt']. Can be either 'highwt'
107
for the highest weight crystal of type A, or 'naive' for an
108
example of a broken crystal.
109
110
- ``**kwds`` -- keyword arguments passed onto the constructor for the
111
chosen crystal.
112
113
EXAMPLES::
114
115
sage: Crystals().example(choice='highwt', n=5)
116
Highest weight crystal of type A_5 of highest weight omega_1
117
sage: Crystals().example(choice='naive')
118
A broken crystal, defined by digraph, of dimension five.
119
"""
120
import sage.categories.examples.crystals as examples
121
if choice == "naive":
122
return examples.NaiveCrystal(**kwds)
123
else:
124
if isinstance(choice, Integer):
125
return examples.HighestWeightCrystalOfTypeA(n=choice, **kwds)
126
else:
127
return examples.HighestWeightCrystalOfTypeA(**kwds)
128
129
class ParentMethods:
130
131
def an_element(self):
132
"""
133
Returns an element of ``self``
134
135
sage: C = CrystalOfLetters(['A', 5])
136
sage: C.an_element()
137
1
138
"""
139
return self.first()
140
141
def weight_lattice_realization(self):
142
"""
143
Returns the weight lattice realization used to express weights.
144
145
This default implementation uses the ambient space of the
146
root system for (non relabelled) finite types and the
147
weight lattice otherwise. This is a legacy from when
148
ambient spaces were partially implemented, and may be
149
changed in the future.
150
151
EXAMPLES::
152
153
sage: C = CrystalOfLetters(['A', 5])
154
sage: C.weight_lattice_realization()
155
Ambient space of the Root system of type ['A', 5]
156
sage: K = KirillovReshetikhinCrystal(['A',2,1], 1, 1)
157
sage: K.weight_lattice_realization()
158
Weight lattice of the Root system of type ['A', 2, 1]
159
"""
160
F = self.cartan_type().root_system()
161
if F.is_finite() and F.ambient_space() is not None:
162
return F.ambient_space()
163
else:
164
return F.weight_lattice()
165
166
def cartan_type(self):
167
"""
168
Returns the Cartan type of the crystal
169
170
EXAMPLES::
171
172
sage: C = CrystalOfLetters(['A',2])
173
sage: C.cartan_type()
174
['A', 2]
175
"""
176
return self._cartan_type
177
178
@cached_method
179
def index_set(self):
180
"""
181
Returns the index set of the Dynkin diagram underlying the crystal
182
183
EXAMPLES::
184
185
sage: C = CrystalOfLetters(['A', 5])
186
sage: C.index_set()
187
(1, 2, 3, 4, 5)
188
"""
189
return self.cartan_type().index_set()
190
191
def Lambda(self):
192
"""
193
Returns the fundamental weights in the weight lattice
194
realization for the root system associated with the crystal
195
196
EXAMPLES::
197
198
sage: C = CrystalOfLetters(['A', 5])
199
sage: C.Lambda()
200
Finite family {1: (1, 0, 0, 0, 0, 0), 2: (1, 1, 0, 0, 0, 0), 3: (1, 1, 1, 0, 0, 0), 4: (1, 1, 1, 1, 0, 0), 5: (1, 1, 1, 1, 1, 0)}
201
"""
202
return self.weight_lattice_realization().fundamental_weights()
203
204
def __iter__(self, index_set=None, max_depth=float('inf')):
205
"""
206
Returns the iterator of ``self``.
207
208
INPUT:
209
210
- ``index_set`` -- (Default: ``None``) The index set; if ``None``
211
then use the index set of the crystal
212
213
- ``max_depth`` -- (Default: infinity) The maximum depth to build
214
215
EXAMPLES::
216
217
sage: C = CrystalOfLSPaths(['A',2,1],[-1,0,1])
218
sage: C.__iter__.__module__
219
'sage.categories.crystals'
220
sage: g = C.__iter__()
221
sage: g.next()
222
(-Lambda[0] + Lambda[2],)
223
sage: g.next()
224
(Lambda[0] - Lambda[1] + delta,)
225
sage: g.next()
226
(Lambda[1] - Lambda[2] + delta,)
227
sage: g.next()
228
(Lambda[1] - Lambda[2],)
229
sage: g.next()
230
(Lambda[0] - Lambda[1],)
231
sage: h = C.__iter__(index_set=[1,2])
232
sage: h.next()
233
(-Lambda[0] + Lambda[2],)
234
sage: h.next()
235
(Lambda[1] - Lambda[2],)
236
sage: h.next()
237
(Lambda[0] - Lambda[1],)
238
sage: h.next()
239
Traceback (most recent call last):
240
...
241
StopIteration
242
sage: g = C.__iter__(max_depth=1)
243
sage: g.next()
244
(-Lambda[0] + Lambda[2],)
245
sage: g.next()
246
(Lambda[1] - Lambda[2],)
247
sage: g.next()
248
(Lambda[0] - Lambda[1] + delta,)
249
sage: h.next()
250
Traceback (most recent call last):
251
...
252
StopIteration
253
254
"""
255
if index_set is None:
256
index_set = self.index_set()
257
if max_depth < float('inf'):
258
from sage.combinat.backtrack import TransitiveIdealGraded
259
return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set]
260
+ [x.e(i) for i in index_set],
261
self.module_generators, max_depth).__iter__()
262
from sage.combinat.backtrack import TransitiveIdeal
263
return TransitiveIdeal(lambda x: [x.f(i) for i in index_set]
264
+ [x.e(i) for i in index_set],
265
self.module_generators).__iter__()
266
267
def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"),
268
direction="both"):
269
r"""
270
Construct the subcrystal from ``generators`` using `e_i` and `f_i`
271
for all `i` in ``index_set``.
272
273
INPUT:
274
275
- ``index_set`` -- (Default: ``None``) The index set; if ``None``
276
then use the index set of the crystal
277
278
- ``generators`` -- (Default: ``None``) The list of generators; if
279
``None`` then use the module generators of the crystal
280
281
- ``max_depth`` -- (Default: infinity) The maximum depth to build
282
283
- ``direction`` -- (Default: ``'both'``) The direction to build
284
the subcrystal. It can be one of the following:
285
286
- ``'both'`` - Using both `e_i` and `f_i`
287
- ``'upper'`` - Using `e_i`
288
- ``'lower'`` - Using `f_i`
289
290
EXAMPLES::
291
292
sage: C = KirillovReshetikhinCrystal(['A',3,1], 1, 2)
293
sage: S = list(C.subcrystal(index_set=[1,2])); S
294
[[[1, 1]], [[1, 2]], [[1, 3]], [[2, 2]], [[2, 3]], [[3, 3]]]
295
sage: C.cardinality()
296
10
297
sage: len(S)
298
6
299
sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)]))
300
[[[1, 4]], [[2, 4]], [[1, 3]], [[2, 3]]]
301
sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], max_depth=1))
302
[[[1, 4]], [[2, 4]], [[1, 3]]]
303
sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='upper'))
304
[[[1, 4]], [[1, 3]]]
305
sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='lower'))
306
[[[1, 4]], [[2, 4]]]
307
"""
308
if index_set is None:
309
index_set = self.index_set()
310
if generators is None:
311
generators = self.module_generators
312
from sage.combinat.backtrack import TransitiveIdealGraded
313
314
if direction == 'both':
315
return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set]
316
+ [x.e(i) for i in index_set],
317
generators, max_depth)
318
if direction == 'upper':
319
return TransitiveIdealGraded(lambda x: [x.e(i) for i in index_set],
320
generators, max_depth)
321
if direction == 'lower':
322
return TransitiveIdealGraded(lambda x: [x.f(i) for i in index_set],
323
generators, max_depth)
324
raise ValueError("direction must be either 'both', 'upper', or 'lower'")
325
326
def crystal_morphism(self, g, index_set = None, automorphism = lambda i : i, direction = 'down', direction_image = 'down',
327
similarity_factor = None, similarity_factor_domain = None, cached = False, acyclic = True):
328
r"""
329
Constructs a morphism from the crystal ``self`` to another crystal.
330
The input `g` can either be a function of a (sub)set of elements of self to
331
element in another crystal or a dictionary between certain elements.
332
Usually one would map highest weight elements or crystal generators to each
333
other using g.
334
Specifying index_set gives the opportunity to define the morphism as `I`-crystals
335
where `I =` index_set. If index_set is not specified, the index set of self is used.
336
It is also possible to define twisted-morphisms by specifying an automorphism on the
337
nodes in te Dynkin diagram (or the index_set).
338
The option direction and direction_image indicate whether to use `f_i` or `e_i` in
339
self or the image crystal to construct the morphism, depending on whether the direction
340
is set to 'down' or 'up'.
341
It is also possible to set a similarity_factor. This should be a dictionary between
342
the elements in the index set and positive integers. The crystal operator `f_i` then gets
343
mapped to `f_i^{m_i}` where `m_i =` similarity_factor[i].
344
Setting similarity_factor_domain to a dictionary between the index set and positive integers
345
has the effect that `f_i^{m_i}` gets mapped to `f_i` where `m_i =` similarity_factor_domain[i].
346
Finally, it is possible to set the option `acyclic = False`. This calculates an isomorphism
347
for cyclic crystals (for example finite affine crystals). In this case the input function `g`
348
is supposed to be given as a dictionary.
349
350
EXAMPLES::
351
352
sage: C2 = CrystalOfLetters(['A',2])
353
sage: C3 = CrystalOfLetters(['A',3])
354
sage: g = {C2.module_generators[0] : C3.module_generators[0]}
355
sage: g_full = C2.crystal_morphism(g)
356
sage: g_full(C2(1))
357
1
358
sage: g_full(C2(2))
359
2
360
sage: g = {C2(1) : C2(3)}
361
sage: g_full = C2.crystal_morphism(g, automorphism = lambda i : 3-i, direction_image = 'up')
362
sage: [g_full(b) for b in C2]
363
[3, 2, 1]
364
sage: T = CrystalOfTableaux(['A',2], shape = [2])
365
sage: g = {C2(1) : T(rows=[[1,1]])}
366
sage: g_full = C2.crystal_morphism(g, similarity_factor = {1:2, 2:2})
367
sage: [g_full(b) for b in C2]
368
[[[1, 1]], [[2, 2]], [[3, 3]]]
369
sage: g = {T(rows=[[1,1]]) : C2(1)}
370
sage: g_full = T.crystal_morphism(g, similarity_factor_domain = {1:2, 2:2})
371
sage: g_full(T(rows=[[2,2]]))
372
2
373
374
sage: B1=KirillovReshetikhinCrystal(['A',2,1],1,1)
375
sage: B2=KirillovReshetikhinCrystal(['A',2,1],1,2)
376
sage: T=TensorProductOfCrystals(B1,B2)
377
sage: T1=TensorProductOfCrystals(B2,B1)
378
sage: La = T.weight_lattice_realization().fundamental_weights()
379
sage: t = [b for b in T if b.weight() == -3*La[0] + 3*La[1]][0]
380
sage: t1 = [b for b in T1 if b.weight() == -3*La[0] + 3*La[1]][0]
381
sage: g={t:t1}
382
sage: f=T.crystal_morphism(g,acyclic = False)
383
sage: [[b,f(b)] for b in T]
384
[[[[[1]], [[1, 1]]], [[[1, 1]], [[1]]]],
385
[[[[1]], [[1, 2]]], [[[1, 1]], [[2]]]],
386
[[[[1]], [[2, 2]]], [[[1, 2]], [[2]]]],
387
[[[[1]], [[1, 3]]], [[[1, 1]], [[3]]]],
388
[[[[1]], [[2, 3]]], [[[1, 2]], [[3]]]],
389
[[[[1]], [[3, 3]]], [[[1, 3]], [[3]]]],
390
[[[[2]], [[1, 1]]], [[[1, 2]], [[1]]]],
391
[[[[2]], [[1, 2]]], [[[2, 2]], [[1]]]],
392
[[[[2]], [[2, 2]]], [[[2, 2]], [[2]]]],
393
[[[[2]], [[1, 3]]], [[[2, 3]], [[1]]]],
394
[[[[2]], [[2, 3]]], [[[2, 2]], [[3]]]],
395
[[[[2]], [[3, 3]]], [[[2, 3]], [[3]]]],
396
[[[[3]], [[1, 1]]], [[[1, 3]], [[1]]]],
397
[[[[3]], [[1, 2]]], [[[1, 3]], [[2]]]],
398
[[[[3]], [[2, 2]]], [[[2, 3]], [[2]]]],
399
[[[[3]], [[1, 3]]], [[[3, 3]], [[1]]]],
400
[[[[3]], [[2, 3]]], [[[3, 3]], [[2]]]],
401
[[[[3]], [[3, 3]]], [[[3, 3]], [[3]]]]]
402
"""
403
if index_set is None:
404
index_set = self.index_set()
405
if similarity_factor is None:
406
similarity_factor = dict( (i,1) for i in index_set )
407
if similarity_factor_domain is None:
408
similarity_factor_domain = dict( (i,1) for i in index_set )
409
if direction == 'down':
410
e_string = 'e_string'
411
else:
412
e_string = 'f_string'
413
if direction_image == 'down':
414
f_string = 'f_string'
415
else:
416
f_string = 'e_string'
417
418
if acyclic:
419
if type(g) == dict:
420
g = g.__getitem__
421
422
def morphism(b):
423
for i in index_set:
424
c = getattr(b, e_string)([i for k in range(similarity_factor_domain[i])])
425
if c is not None:
426
d = getattr(morphism(c), f_string)([automorphism(i) for k in range(similarity_factor[i])])
427
if d is not None:
428
return d
429
else:
430
raise ValueError, "This is not a morphism!"
431
#now we know that b is hw
432
return g(b)
433
434
else:
435
import copy
436
morphism = copy.copy(g)
437
known = set( g.keys() )
438
todo = copy.copy(known)
439
images = set( [g[x] for x in known] )
440
# Invariants:
441
# - images contains all known morphism(x)
442
# - known contains all elements x for which we know morphism(x)
443
# - todo contains all elements x for which we haven't propagated to each child
444
while todo != set( [] ):
445
x = todo.pop()
446
for i in index_set:
447
eix = getattr(x, f_string)([i for k in range(similarity_factor_domain[i])])
448
eigx = getattr(morphism[x], f_string)([automorphism(i) for k in range(similarity_factor[i])])
449
if bool(eix is None) != bool(eigx is None):
450
# This is not a crystal morphism!
451
raise ValueError, "This is not a morphism!" #, print("x="x,"g(x)="g(x),"i="i)
452
if (eix is not None) and (eix not in known):
453
todo.add(eix)
454
known.add(eix)
455
morphism[eix] = eigx
456
images.add(eigx)
457
# Check that the module generators are indeed module generators
458
assert(len(known) == self.cardinality())
459
# We may not want to systematically run those tests,
460
# to allow for non bijective crystal morphism
461
# Add an option CheckBijective?
462
if not ( len(known) == len(images) and len(images) == images.pop().parent().cardinality() ):
463
return(None)
464
return morphism.__getitem__
465
466
if cached:
467
return morphism
468
else:
469
return CachedFunction(morphism)
470
471
def digraph(self, subset=None, index_set=None):
472
"""
473
Returns the DiGraph associated to ``self``.
474
475
INPUT:
476
477
- ``subset`` -- (Optional) A subset of vertices for
478
which the digraph should be constructed
479
480
- ``index_set`` -- (Optional) The index set to draw arrows
481
482
EXAMPLES::
483
484
sage: C = Crystals().example(5)
485
sage: C.digraph()
486
Digraph on 6 vertices
487
488
The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2,
489
and green for edge 3::
490
491
sage: C = Crystals().example(3)
492
sage: G = C.digraph()
493
sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz
494
495
One may also overwrite the colors::
496
497
sage: C = Crystals().example(3)
498
sage: G = C.digraph()
499
sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"})
500
sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz
501
502
Or one may add colors to yet unspecified edges::
503
504
sage: C = Crystals().example(4)
505
sage: G = C.digraph()
506
sage: C.cartan_type()._index_set_coloring[4]="purple"
507
sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz
508
509
Here is an example of how to take the top part up to a given depth of an infinite dimensional
510
crystal::
511
512
sage: C = CartanType(['C',2,1])
513
sage: La = C.root_system().weight_lattice().fundamental_weights()
514
sage: T = HighestWeightCrystal(La[0])
515
sage: S = T.subcrystal(max_depth=3)
516
sage: G = T.digraph(subset=S); G
517
Digraph on 5 vertices
518
sage: G.vertices()
519
[(1/2*Lambda[0] + Lambda[1] - Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta),
520
(-Lambda[0] + 2*Lambda[1] - delta,), (Lambda[0] - 2*Lambda[1] + 2*Lambda[2] - delta,),
521
(1/2*Lambda[0] - Lambda[1] + Lambda[2] - 1/2*delta, -1/2*Lambda[0] + Lambda[1] - 1/2*delta), (Lambda[0],)]
522
523
Here is a way to construct a picture of a Demazure crystal using
524
the ``subset`` option::
525
526
sage: B = CrystalOfTableaux(['A',2], shape=[2,1])
527
sage: C = CombinatorialFreeModule(QQ,B)
528
sage: t = B.highest_weight_vector()
529
sage: b = C(t)
530
sage: D = B.demazure_operator(b,[2,1]); D
531
B[[[1, 1], [2]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 1], [3]]] + B[[[1, 3], [3]]]
532
sage: G = B.digraph(subset=D.support())
533
sage: G.vertices()
534
[[[1, 1], [2]], [[1, 2], [2]], [[1, 3], [2]], [[1, 1], [3]], [[1, 3], [3]]]
535
sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz
536
537
We can also choose to display particular arrows using the
538
``index_set`` option::
539
540
sage: C = KirillovReshetikhinCrystal(['D',4,1], 2, 1)
541
sage: G = C.digraph(index_set=[1,3])
542
sage: len(G.edges())
543
20
544
sage: view(G, pdflatex=True, tightpage=True) #optional - dot2tex graphviz
545
546
TODO: add more tests
547
"""
548
from sage.graphs.all import DiGraph
549
from sage.categories.highest_weight_crystals import HighestWeightCrystals
550
d = {}
551
if self in HighestWeightCrystals:
552
f = lambda (u,v,label): ({})
553
else:
554
f = lambda (u,v,label): ({"backward":label ==0})
555
556
# Parse optional arguments
557
if subset is None:
558
subset = self
559
if index_set is None:
560
index_set = self.index_set()
561
562
for x in subset:
563
d[x] = {}
564
for i in index_set:
565
child = x.f(i)
566
if child is None or child not in subset:
567
continue
568
d[x][child]=i
569
G = DiGraph(d)
570
if have_dot2tex():
571
G.set_latex_options(format="dot2tex",
572
edge_labels = True,
573
color_by_label = self.cartan_type()._index_set_coloring,
574
edge_options = f)
575
return G
576
577
def latex_file(self, filename):
578
r"""
579
Exports a file, suitable for pdflatex, to 'filename'. This requires
580
a proper installation of ``dot2tex`` in sage-python. For more
581
information see the documentation for ``self.latex()``.
582
583
EXAMPLES::
584
585
sage: C = CrystalOfLetters(['A', 5])
586
sage: C.latex_file('/tmp/test.tex') #optional - dot2tex
587
"""
588
header = r"""\documentclass{article}
589
\usepackage[x11names, rgb]{xcolor}
590
\usepackage[utf8]{inputenc}
591
\usepackage{tikz}
592
\usetikzlibrary{snakes,arrows,shapes}
593
\usepackage{amsmath}
594
\usepackage[active,tightpage]{preview}
595
\newenvironment{bla}{}{}
596
\PreviewEnvironment{bla}
597
598
\begin{document}
599
\begin{bla}"""
600
601
footer = r"""\end{bla}
602
\end{document}"""
603
604
f = open(filename, 'w')
605
f.write(header + self.latex() + footer)
606
f.close()
607
608
def _latex_(self, **options):
609
r"""
610
Returns the crystal graph as a latex string. This can be exported
611
to a file with self.latex_file('filename').
612
613
EXAMPLES::
614
615
sage: T = CrystalOfTableaux(['A',2],shape=[1])
616
sage: T._latex_() #optional - dot2tex
617
'...tikzpicture...'
618
sage: view(T, pdflatex = True, tightpage = True) #optional - dot2tex graphviz
619
620
One can for example also color the edges using the following options::
621
622
sage: T = CrystalOfTableaux(['A',2],shape=[1])
623
sage: T._latex_(color_by_label = {0:"black", 1:"red", 2:"blue"}) #optional - dot2tex graphviz
624
'...tikzpicture...'
625
"""
626
if not have_dot2tex():
627
print "dot2tex not available. Install after running \'sage -sh\'"
628
return
629
G=self.digraph()
630
G.set_latex_options(**options)
631
return G._latex_()
632
633
latex = _latex_
634
635
def metapost(self, filename, thicklines=False, labels=True, scaling_factor=1.0, tallness=1.0):
636
r"""
637
Use C.metapost("filename.mp",[options]), where options can be:
638
639
thicklines = True (for thicker edges) labels = False (to suppress
640
labeling of the vertices) scaling_factor=value, where value is a
641
floating point number, 1.0 by default. Increasing or decreasing the
642
scaling factor changes the size of the image. tallness=1.0.
643
Increasing makes the image taller without increasing the width.
644
645
Root operators e(1) or f(1) move along red lines, e(2) or f(2)
646
along green. The highest weight is in the lower left. Vertices with
647
the same weight are kept close together. The concise labels on the
648
nodes are strings introduced by Berenstein and Zelevinsky and
649
Littelmann; see Littelmann's paper Cones, Crystals, Patterns,
650
sections 5 and 6.
651
652
For Cartan types B2 or C2, the pattern has the form
653
654
a2 a3 a4 a1
655
656
where c\*a2 = a3 = 2\*a4 =0 and a1=0, with c=2 for B2, c=1 for C2.
657
Applying e(2) a1 times, e(1) a2 times, e(2) a3 times, e(1) a4 times
658
returns to the highest weight. (Observe that Littelmann writes the
659
roots in opposite of the usual order, so our e(1) is his e(2) for
660
these Cartan types.) For type A2, the pattern has the form
661
662
a3 a2 a1
663
664
where applying e(1) a1 times, e(2) a2 times then e(3) a1 times
665
returns to the highest weight. These data determine the vertex and
666
may be translated into a Gelfand-Tsetlin pattern or tableau.
667
668
EXAMPLES::
669
670
sage: C = CrystalOfLetters(['A', 2])
671
sage: C.metapost('/tmp/test.mp') #optional
672
673
::
674
675
sage: C = CrystalOfLetters(['A', 5])
676
sage: C.metapost('/tmp/test.mp')
677
Traceback (most recent call last):
678
...
679
NotImplementedError
680
"""
681
# FIXME: those tests are not robust
682
# Should use instead self.cartan_type() == CartanType(['B',2])
683
if self.cartan_type()[0] == 'B' and self.cartan_type()[1] == 2:
684
word = [2,1,2,1]
685
elif self.cartan_type()[0] == 'C' and self.cartan_type()[1] == 2:
686
word = [2,1,2,1]
687
elif self.cartan_type()[0] == 'A' and self.cartan_type()[1] == 2:
688
word = [1,2,1]
689
else:
690
raise NotImplementedError
691
size = self.cardinality()
692
string_data = []
693
for i in range(size):
694
turtle = self.list()[i]
695
string_datum = []
696
for j in word:
697
turtlewalk = 0
698
while not turtle.e(j) == None:
699
turtle = turtle.e(j)
700
turtlewalk += 1
701
string_datum.append(turtlewalk)
702
string_data.append(string_datum)
703
704
if self.cartan_type()[0] == 'A':
705
if labels:
706
c0 = int(55*scaling_factor)
707
c1 = int(-25*scaling_factor)
708
c2 = int(45*tallness*scaling_factor)
709
c3 = int(-12*scaling_factor)
710
c4 = int(-12*scaling_factor)
711
else:
712
c0 = int(45*scaling_factor)
713
c1 = int(-20*scaling_factor)
714
c2 = int(35*tallness*scaling_factor)
715
c3 = int(12*scaling_factor)
716
c4 = int(-12*scaling_factor)
717
outstring = "verbatimtex\n\\magnification=600\netex\n\nbeginfig(-1);\nsx:=35; sy:=30;\n\nz1000=(%d,0);\nz1001=(%d,%d);\nz1002=(%d,%d);\nz2001=(-3,3);\nz2002=(3,3);\nz2003=(0,-3);\nz2004=(7,0);\nz2005=(0,7);\nz2006=(-7,0);\nz2007=(0,7);\n\n"%(c0,c1,c2,c3,c4)
718
else:
719
if labels:
720
outstring = "verbatimtex\n\\magnification=600\netex\n\nbeginfig(-1);\n\nsx := %d;\nsy=%d;\n\nz1000=(2*sx,0);\nz1001=(-sx,sy);\nz1002=(-16,-10);\n\nz2001=(0,-3);\nz2002=(-5,3);\nz2003=(0,3);\nz2004=(5,3);\nz2005=(10,1);\nz2006=(0,10);\nz2007=(-10,1);\nz2008=(0,-8);\n\n"%(int(scaling_factor*40),int(tallness*scaling_factor*40))
721
else:
722
outstring = "beginfig(-1);\n\nsx := %d;\nsy := %d;\n\nz1000=(2*sx,0);\nz1001=(-sx,sy);\nz1002=(-5,-5);\n\nz1003=(10,10);\n\n"%(int(scaling_factor*35),int(tallness*scaling_factor*35))
723
for i in range(size):
724
if self.cartan_type()[0] == 'A':
725
[a1,a2,a3] = string_data[i]
726
else:
727
[a1,a2,a3,a4] = string_data[i]
728
shift = 0
729
for j in range(i):
730
if self.cartan_type()[0] == 'A':
731
[b1,b2,b3] = string_data[j]
732
if b1+b3 == a1+a3 and b2 == a2:
733
shift += 1
734
else:
735
[b1,b2,b3,b4] = string_data[j]
736
if b1+b3 == a1+a3 and b2+b4 == a2+a4:
737
shift += 1
738
if self.cartan_type()[0] == 'A':
739
outstring = outstring +"z%d=%d*z1000+%d*z1001+%d*z1002;\n"%(i,a1+a3,a2,shift)
740
else:
741
outstring = outstring +"z%d=%d*z1000+%d*z1001+%d*z1002;\n"%(i,a1+a3,a2+a4,shift)
742
outstring = outstring + "\n"
743
if thicklines:
744
outstring = outstring +"pickup pencircle scaled 2\n\n"
745
for i in range(size):
746
for j in range(1,3):
747
dest = self.list()[i].f(j)
748
if not dest == None:
749
dest = self.list().index(dest)
750
if j == 1:
751
col = "red;"
752
else:
753
col = "green; "
754
if self.cartan_type()[0] == 'A':
755
[a1,a2,a3] = string_data[i] # included to facilitate hand editing of the .mp file
756
outstring = outstring+"draw z%d--z%d withcolor %s %% %d %d %d\n"%(i,dest,col,a1,a2,a3)
757
else:
758
[a1,a2,a3,a4] = string_data[i]
759
outstring = outstring+"draw z%d--z%d withcolor %s %% %d %d %d %d\n"%(i,dest,col,a1,a2,a3,a4)
760
outstring += "\npickup pencircle scaled 3;\n\n"
761
for i in range(self.cardinality()):
762
if labels:
763
if self.cartan_type()[0] == 'A':
764
outstring = outstring+"pickup pencircle scaled 15;\nfill z%d+z2004..z%d+z2006..z%d+z2006..z%d+z2007..cycle withcolor white;\nlabel(btex %d etex, z%d+z2001);\nlabel(btex %d etex, z%d+z2002);\nlabel(btex %d etex, z%d+z2003);\npickup pencircle scaled .5;\ndraw z%d+z2004..z%d+z2006..z%d+z2006..z%d+z2007..cycle;\n"%(i,i,i,i,string_data[i][2],i,string_data[i][1],i,string_data[i][0],i,i,i,i,i)
765
else:
766
outstring = outstring+"%%%d %d %d %d\npickup pencircle scaled 1;\nfill z%d+z2005..z%d+z2006..z%d+z2007..z%d+z2008..cycle withcolor white;\nlabel(btex %d etex, z%d+z2001);\nlabel(btex %d etex, z%d+z2002);\nlabel(btex %d etex, z%d+z2003);\nlabel(btex %d etex, z%d+z2004);\npickup pencircle scaled .5;\ndraw z%d+z2005..z%d+z2006..z%d+z2007..z%d+z2008..cycle;\n\n"%(string_data[i][0],string_data[i][1],string_data[i][2],string_data[i][3],i,i,i,i,string_data[i][0],i,string_data[i][1],i,string_data[i][2],i,string_data[i][3],i,i,i,i,i)
767
else:
768
outstring += "drawdot z%d;\n"%i
769
outstring += "\nendfig;\n\nend;\n\n"
770
771
f = open(filename, 'w')
772
f.write(outstring)
773
f.close()
774
775
def dot_tex(self):
776
r"""
777
Returns a dot_tex string representation of ``self``.
778
779
EXAMPLES::
780
781
sage: C = CrystalOfLetters(['A',2])
782
sage: C.dot_tex()
783
'digraph G { \n node [ shape=plaintext ];\n N_0 [ label = " ", texlbl = "$1$" ];\n N_1 [ label = " ", texlbl = "$2$" ];\n N_2 [ label = " ", texlbl = "$3$" ];\n N_0 -> N_1 [ label = " ", texlbl = "1" ];\n N_1 -> N_2 [ label = " ", texlbl = "2" ];\n}'
784
"""
785
import re
786
rank = ranker.from_list(self.list())[0]
787
vertex_key = lambda x: "N_"+str(rank(x))
788
789
# To do: check the regular expression
790
# Removing %-style comments, newlines, quotes
791
# This should probably be moved to sage.misc.latex
792
quoted_latex = lambda x: re.sub("\"|\r|(%[^\n]*)?\n","", latex(x))
793
794
result = "digraph G { \n node [ shape=plaintext ];\n"
795
796
for x in self:
797
result += " " + vertex_key(x) + " [ label = \" \", texlbl = \"$"+quoted_latex(x)+"$\" ];\n"
798
for x in self:
799
for i in self.index_set():
800
child = x.f(i)
801
if child is None:
802
continue
803
# result += " " + vertex_key(x) + " -> "+vertex_key(child)+ " [ label = \" \", texlbl = \""+quoted_latex(i)+"\" ];\n"
804
if i == 0:
805
option = "dir = back, "
806
(source, target) = (child, x)
807
else:
808
option = ""
809
(source, target) = (x, child)
810
result += " " + vertex_key(source) + " -> "+vertex_key(target)+ " [ "+option+"label = \" \", texlbl = \""+quoted_latex(i)+"\" ];\n"
811
result+="}"
812
return result
813
814
def plot(self, **options):
815
"""
816
Returns the plot of self as a directed graph.
817
818
EXAMPLES::
819
820
sage: C = CrystalOfLetters(['A', 5])
821
sage: print(C.plot())
822
Graphics object consisting of 17 graphics primitives
823
"""
824
return self.digraph().plot(edge_labels=True,vertex_size=0,**options)
825
826
def plot3d(self, **options):
827
"""
828
Returns the 3-dimensional plot of self as a directed graph.
829
830
EXAMPLES::
831
832
sage: C = KirillovReshetikhinCrystal(['A',3,1],2,1)
833
sage: print(C.plot3d())
834
Graphics3d Object
835
"""
836
G = self.digraph(**options)
837
return G.plot3d()
838
839
840
class ElementMethods:
841
842
@cached_method
843
def index_set(self):
844
"""
845
EXAMPLES::
846
847
sage: C = CrystalOfLetters(['A',5])
848
sage: C(1).index_set()
849
(1, 2, 3, 4, 5)
850
"""
851
return self.parent().index_set()
852
853
def cartan_type(self):
854
"""
855
Returns the cartan type associated to ``self``
856
857
EXAMPLES::
858
859
sage: C = CrystalOfLetters(['A', 5])
860
sage: C(1).cartan_type()
861
['A', 5]
862
"""
863
return self.parent().cartan_type()
864
865
@abstract_method
866
def e(self, i):
867
r"""
868
Returns `e_i(x)` if it exists or ``None`` otherwise.
869
870
This method should be implemented by the element class of
871
the crystal.
872
873
EXAMPLES::
874
875
sage: C = Crystals().example(5)
876
sage: x = C[2]; x
877
3
878
sage: x.e(1), x.e(2), x.e(3)
879
(None, 2, None)
880
"""
881
882
@abstract_method
883
def f(self, i):
884
r"""
885
Returns `f_i(x)` if it exists or ``None`` otherwise.
886
887
This method should be implemented by the element class of
888
the crystal.
889
890
EXAMPLES::
891
892
sage: C = Crystals().example(5)
893
sage: x = C[1]; x
894
2
895
sage: x.f(1), x.f(2), x.f(3)
896
(None, 3, None)
897
"""
898
899
@abstract_method
900
def epsilon(self, i):
901
r"""
902
EXAMPLES::
903
904
sage: C = CrystalOfLetters(['A',5])
905
sage: C(1).epsilon(1)
906
0
907
sage: C(2).epsilon(1)
908
1
909
"""
910
911
@abstract_method
912
def phi(self, i):
913
r"""
914
EXAMPLES::
915
916
sage: C = CrystalOfLetters(['A',5])
917
sage: C(1).phi(1)
918
1
919
sage: C(2).phi(1)
920
0
921
"""
922
923
@abstract_method
924
def weight(self):
925
r"""
926
Returns the weight of this crystal element
927
928
This method should be implemented by the element class of
929
the crystal.
930
931
EXAMPLES::
932
933
sage: C = CrystalOfLetters(['A',5])
934
sage: C(1).weight()
935
(1, 0, 0, 0, 0, 0)
936
"""
937
938
def phi_minus_epsilon(self, i):
939
"""
940
Returns `\phi_i - \epsilon_i` of self. There are sometimes
941
better implementations using the weight for this. It is used
942
for reflections along a string.
943
944
EXAMPLES::
945
946
sage: C = CrystalOfLetters(['A',5])
947
sage: C(1).phi_minus_epsilon(1)
948
1
949
"""
950
return self.phi(i) - self.epsilon(i)
951
952
def Epsilon(self):
953
"""
954
EXAMPLES::
955
956
sage: C = CrystalOfLetters(['A',5])
957
sage: C(0).Epsilon()
958
(0, 0, 0, 0, 0, 0)
959
sage: C(1).Epsilon()
960
(0, 0, 0, 0, 0, 0)
961
sage: C(2).Epsilon()
962
(1, 0, 0, 0, 0, 0)
963
"""
964
Lambda = self.parent().Lambda()
965
return sum(self.epsilon(i) * Lambda[i] for i in self.index_set())
966
967
def Phi(self):
968
"""
969
EXAMPLES::
970
971
sage: C = CrystalOfLetters(['A',5])
972
sage: C(0).Phi()
973
(0, 0, 0, 0, 0, 0)
974
sage: C(1).Phi()
975
(1, 0, 0, 0, 0, 0)
976
sage: C(2).Phi()
977
(1, 1, 0, 0, 0, 0)
978
"""
979
Lambda = self.parent().Lambda()
980
return sum(self.phi(i) * Lambda[i] for i in self.index_set())
981
982
def f_string(self, list):
983
r"""
984
Applies `f_{i_r} ... f_{i_1}` to self for `list = [i_1, ..., i_r]`
985
986
EXAMPLES::
987
988
sage: C = CrystalOfLetters(['A',3])
989
sage: b = C(1)
990
sage: b.f_string([1,2])
991
3
992
sage: b.f_string([2,1])
993
"""
994
b = self
995
for i in list:
996
b = b.f(i)
997
if b is None:
998
return None
999
return b
1000
1001
def e_string(self, list):
1002
r"""
1003
Applies `e_{i_r} ... e_{i_1}` to self for `list = [i_1, ..., i_r]`
1004
1005
EXAMPLES::
1006
1007
sage: C = CrystalOfLetters(['A',3])
1008
sage: b = C(3)
1009
sage: b.e_string([2,1])
1010
1
1011
sage: b.e_string([1,2])
1012
"""
1013
b = self
1014
for i in list:
1015
b = b.e(i)
1016
if b is None:
1017
return None
1018
return b
1019
1020
def s(self, i):
1021
r"""
1022
Returns the reflection of ``self`` along its `i`-string
1023
1024
EXAMPLES::
1025
1026
sage: C = CrystalOfTableaux(['A',2], shape=[2,1])
1027
sage: b=C(rows=[[1,1],[3]])
1028
sage: b.s(1)
1029
[[2, 2], [3]]
1030
sage: b=C(rows=[[1,2],[3]])
1031
sage: b.s(2)
1032
[[1, 2], [3]]
1033
sage: T=CrystalOfTableaux(['A',2],shape=[4])
1034
sage: t=T(rows=[[1,2,2,2]])
1035
sage: t.s(1)
1036
[[1, 1, 1, 2]]
1037
"""
1038
d = self.phi_minus_epsilon(i)
1039
b = self
1040
if d > 0:
1041
for j in range(d):
1042
b = b.f(i)
1043
else:
1044
for j in range(-d):
1045
b = b.e(i)
1046
return b
1047
1048
def is_highest_weight(self, index_set = None):
1049
r"""
1050
Returns ``True`` if ``self`` is a highest weight.
1051
Specifying the option ``index_set`` to be a subset `I` of the
1052
index set of the underlying crystal, finds all highest
1053
weight vectors for arrows in `I`.
1054
1055
EXAMPLES::
1056
1057
sage: C = CrystalOfLetters(['A',5])
1058
sage: C(1).is_highest_weight()
1059
True
1060
sage: C(2).is_highest_weight()
1061
False
1062
sage: C(2).is_highest_weight(index_set = [2,3,4,5])
1063
True
1064
"""
1065
if index_set is None:
1066
index_set = self.index_set()
1067
return all(self.e(i) is None for i in index_set)
1068
1069
def is_lowest_weight(self, index_set = None):
1070
r"""
1071
Returns ``True`` if ``self`` is a lowest weight.
1072
Specifying the option ``index_set`` to be a subset `I` of the
1073
index set of the underlying crystal, finds all lowest
1074
weight vectors for arrows in `I`.
1075
1076
EXAMPLES::
1077
1078
sage: C = CrystalOfLetters(['A',5])
1079
sage: C(1).is_lowest_weight()
1080
False
1081
sage: C(6).is_lowest_weight()
1082
True
1083
sage: C(4).is_lowest_weight(index_set = [1,3])
1084
True
1085
"""
1086
if index_set is None:
1087
index_set = self.index_set()
1088
return all(self.f(i) is None for i in index_set)
1089
1090
def to_highest_weight(self, index_set = None):
1091
r"""
1092
Yields the highest weight element `u` and a list `[i_1,...,i_k]`
1093
such that `self = f_{i_1} ... f_{i_k} u`, where `i_1,...,i_k` are
1094
elements in `index_set`. By default the index set is assumed to be
1095
the full index set of self.
1096
1097
EXAMPLES::
1098
1099
sage: T = CrystalOfTableaux(['A',3], shape = [1])
1100
sage: t = T(rows = [[3]])
1101
sage: t.to_highest_weight()
1102
[[[1]], [2, 1]]
1103
sage: T = CrystalOfTableaux(['A',3], shape = [2,1])
1104
sage: t = T(rows = [[1,2],[4]])
1105
sage: t.to_highest_weight()
1106
[[[1, 1], [2]], [1, 3, 2]]
1107
sage: t.to_highest_weight(index_set = [3])
1108
[[[1, 2], [3]], [3]]
1109
sage: K = KirillovReshetikhinCrystal(['A',3,1],2,1)
1110
sage: t = K(rows=[[2],[3]]); t.to_highest_weight(index_set=[1])
1111
[[[1], [3]], [1]]
1112
sage: t.to_highest_weight()
1113
Traceback (most recent call last):
1114
...
1115
ValueError: This is not a highest weight crystals!
1116
"""
1117
from sage.categories.highest_weight_crystals import HighestWeightCrystals
1118
if index_set is None:
1119
if HighestWeightCrystals() not in self.parent().categories():
1120
raise ValueError("This is not a highest weight crystals!")
1121
index_set = self.index_set()
1122
for i in index_set:
1123
next = self.e(i)
1124
if next is not None:
1125
hw = next.to_highest_weight(index_set = index_set)
1126
return [hw[0], [i] + hw[1]]
1127
return [self, []]
1128
1129
def to_lowest_weight(self, index_set = None):
1130
r"""
1131
Yields the lowest weight element `u` and a list `[i_1,...,i_k]`
1132
such that `self = e_{i_1} ... e_{i_k} u`, where `i_1,...,i_k` are
1133
elements in `index_set`. By default the index set is assumed to be
1134
the full index set of self.
1135
1136
EXAMPLES::
1137
1138
sage: T = CrystalOfTableaux(['A',3], shape = [1])
1139
sage: t = T(rows = [[3]])
1140
sage: t.to_lowest_weight()
1141
[[[4]], [3]]
1142
sage: T = CrystalOfTableaux(['A',3], shape = [2,1])
1143
sage: t = T(rows = [[1,2],[4]])
1144
sage: t.to_lowest_weight()
1145
[[[3, 4], [4]], [1, 2, 2, 3]]
1146
sage: t.to_lowest_weight(index_set = [3])
1147
[[[1, 2], [4]], []]
1148
sage: K = KirillovReshetikhinCrystal(['A',3,1],2,1)
1149
sage: t = K.module_generator(); t
1150
[[1], [2]]
1151
sage: t.to_lowest_weight(index_set=[1,2,3])
1152
[[[3], [4]], [2, 1, 3, 2]]
1153
sage: t.to_lowest_weight()
1154
Traceback (most recent call last):
1155
...
1156
ValueError: This is not a highest weight crystals!
1157
"""
1158
from sage.categories.highest_weight_crystals import HighestWeightCrystals
1159
if index_set is None:
1160
if HighestWeightCrystals() not in self.parent().categories():
1161
raise ValueError, "This is not a highest weight crystals!"
1162
index_set = self.index_set()
1163
for i in index_set:
1164
next = self.f(i)
1165
if next is not None:
1166
lw = next.to_lowest_weight(index_set = index_set)
1167
return [lw[0], [i] + lw[1]]
1168
return [self, []]
1169
1170
def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both"):
1171
r"""
1172
Construct the subcrystal generated by ``self`` using `e_i` and/or
1173
`f_i` for all `i` in ``index_set``.
1174
1175
INPUT:
1176
1177
- ``index_set`` -- (Default: ``None``) The index set; if ``None``
1178
then use the index set of the crystal
1179
1180
- ``max_depth`` -- (Default: infinity) The maximum depth to build
1181
1182
- ``direction`` -- (Default: ``'both'``) The direction to build
1183
the subcrystal. It can be one of the following:
1184
1185
- ``'both'`` - Using both `e_i` and `f_i`
1186
- ``'upper'`` - Using `e_i`
1187
- ``'lower'`` - Using `f_i`
1188
1189
.. SEEALSO::
1190
1191
- :meth:`Crystals.ParentMethods.subcrystal()`.
1192
1193
EXAMPLES::
1194
1195
sage: C = KirillovReshetikhinCrystal(['A',3,1], 1, 2)
1196
sage: elt = C(1,4)
1197
sage: list(elt.subcrystal(index_set=[1,3]))
1198
[[[1, 4]], [[2, 4]], [[1, 3]], [[2, 3]]]
1199
sage: list(elt.subcrystal(index_set=[1,3], max_depth=1))
1200
[[[1, 4]], [[2, 4]], [[1, 3]]]
1201
sage: list(elt.subcrystal(index_set=[1,3], direction='upper'))
1202
[[[1, 4]], [[1, 3]]]
1203
sage: list(elt.subcrystal(index_set=[1,3], direction='lower'))
1204
[[[1, 4]], [[2, 4]]]
1205
"""
1206
return self.parent().subcrystal(generators=[self], index_set=index_set,
1207
max_depth=max_depth, direction=direction)
1208
1209
1210