summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichele Borassi <michele.borassi@imtlucca.it>2015-07-30 17:52:39 +0200
committerMichele Borassi <michele.borassi@imtlucca.it>2015-07-30 17:52:39 +0200
commitea6a7f871165a41c3773605acf207322c63b5314 (patch)
treef7d80b2006f23a82c409002d18e74eca9ff6aba3
parentRemoved trailing whitespaces (diff)
parentUpdated Sage version to 6.9.beta0 (diff)
Merged with 6.9.beta0
-rw-r--r--VERSION.txt2
-rw-r--r--build/pkgs/configure/checksums.ini6
-rw-r--r--build/pkgs/configure/package-version.txt2
-rw-r--r--build/pkgs/freetype/checksums.ini6
-rw-r--r--build/pkgs/freetype/package-version.txt2
-rwxr-xr-xsrc/bin/sage9
-rw-r--r--src/bin/sage-banner5
-rw-r--r--src/bin/sage-env5
-rw-r--r--src/bin/sage-version.sh4
-rw-r--r--src/doc/en/reference/combinat/module_list.rst1
-rw-r--r--src/doc/en/reference/misc/index.rst1
-rw-r--r--src/sage/categories/crystals.py1582
-rw-r--r--src/sage/categories/finite_posets.py3
-rw-r--r--src/sage/categories/highest_weight_crystals.py198
-rw-r--r--src/sage/categories/regular_crystals.py92
-rw-r--r--src/sage/coding/codes_catalog.py29
-rw-r--r--src/sage/combinat/crystals/catalog.py3
-rw-r--r--src/sage/combinat/crystals/direct_sum.py58
-rw-r--r--src/sage/combinat/crystals/elementary_crystals.py1
-rw-r--r--src/sage/combinat/crystals/generalized_young_walls.py10
-rw-r--r--src/sage/combinat/crystals/induced_structure.py689
-rw-r--r--src/sage/combinat/crystals/infinity_crystals.py23
-rw-r--r--src/sage/combinat/crystals/kirillov_reshetikhin.py385
-rw-r--r--src/sage/combinat/crystals/subcrystal.py397
-rw-r--r--src/sage/combinat/crystals/tensor_product.py15
-rw-r--r--src/sage/combinat/crystals/virtual_crystal.py405
-rw-r--r--src/sage/combinat/designs/difference_family.py18
-rw-r--r--src/sage/combinat/designs/incidence_structures.py33
-rw-r--r--src/sage/combinat/finite_state_machine.py201
-rw-r--r--src/sage/combinat/matrices/all.py1
-rw-r--r--src/sage/combinat/matrices/dancing_links.pyx231
-rw-r--r--src/sage/combinat/matrices/dancing_links_c.h16
-rw-r--r--src/sage/combinat/posets/hasse_diagram.py17
-rw-r--r--src/sage/combinat/posets/posets.py124
-rw-r--r--src/sage/combinat/rigged_configurations/bij_infinity.py8
-rw-r--r--src/sage/combinat/rigged_configurations/rigged_configuration_element.py23
-rw-r--r--src/sage/combinat/root_system/cartan_matrix.py19
-rw-r--r--src/sage/combinat/root_system/cartan_type.py19
-rw-r--r--src/sage/combinat/root_system/dynkin_diagram.py20
-rw-r--r--src/sage/combinat/tiling.py13
-rw-r--r--src/sage/doctest/sources.py2
-rw-r--r--src/sage/ext/memory_allocator.pxd14
-rw-r--r--src/sage/ext/memory_allocator.pyx109
-rw-r--r--src/sage/functions/prime_pi.pyx19
-rw-r--r--src/sage/games/quantumino.py2
-rw-r--r--src/sage/geometry/polyhedron/base.py142
-rw-r--r--src/sage/geometry/polyhedron/base_ZZ.py41
-rw-r--r--src/sage/geometry/polyhedron/cdd_file_format.py54
-rw-r--r--src/sage/graphs/asteroidal_triples.pyx22
-rw-r--r--src/sage/graphs/centrality.pyx1
-rw-r--r--src/sage/graphs/chrompoly.pyx106
-rw-r--r--src/sage/graphs/distances_all_pairs.pxd6
-rw-r--r--src/sage/graphs/distances_all_pairs.pyx229
-rw-r--r--src/sage/graphs/generic_graph.py180
-rw-r--r--src/sage/graphs/graph.py17
-rw-r--r--src/sage/graphs/graph_decompositions/bandwidth.pyx81
-rw-r--r--src/sage/graphs/graph_decompositions/cutwidth.pyx262
-rw-r--r--src/sage/graphs/graph_decompositions/vertex_separation.pyx2
-rw-r--r--src/sage/graphs/hyperbolicity.pyx25
-rw-r--r--src/sage/graphs/mcqd.pyx17
-rw-r--r--src/sage/graphs/spanning_tree.pyx190
-rw-r--r--src/sage/libs/pari/closure.pyx2
-rw-r--r--src/sage/libs/pari/declinl.pxi2
-rw-r--r--src/sage/libs/pari/gen.pxd3
-rw-r--r--src/sage/libs/pari/gen.pyx2
-rw-r--r--src/sage/libs/pari/handle_error.pxd2
-rw-r--r--src/sage/libs/pari/handle_error.pyx4
-rw-r--r--src/sage/libs/pari/pari_instance.pxd3
-rw-r--r--src/sage/libs/pari/pari_instance.pyx11
-rw-r--r--src/sage/libs/pari/paridecl.pxd112
-rw-r--r--src/sage/libs/pari/paripriv.pxd30
-rw-r--r--src/sage/libs/pari/types.pxd82
-rw-r--r--src/sage/matrix/matrix_integer_dense.pyx2
-rw-r--r--src/sage/matrix/matrix_rational_dense.pyx2
-rw-r--r--src/sage/matroids/matroid.pxd3
-rw-r--r--src/sage/matroids/matroid.pyx137
-rw-r--r--src/sage/misc/cython_metaclass.h2
-rw-r--r--src/sage/misc/lazy_format.py21
-rw-r--r--src/sage/misc/lazy_string.pxd6
-rw-r--r--src/sage/misc/lazy_string.pyx (renamed from src/sage/misc/lazy_string.py)302
-rw-r--r--src/sage/misc/rest_index_of_methods.py146
-rw-r--r--src/sage/plot/plot3d/base.pyx269
-rw-r--r--src/sage/rings/complex_double.pxd6
-rw-r--r--src/sage/rings/complex_double.pyx2
-rw-r--r--src/sage/rings/continued_fraction.py38
-rw-r--r--src/sage/rings/fast_arith.pyx5
-rw-r--r--src/sage/rings/finite_rings/element_givaro.pyx2
-rw-r--r--src/sage/rings/finite_rings/element_ntl_gf2e.pyx2
-rw-r--r--src/sage/rings/finite_rings/element_pari_ffelt.pxd3
-rw-r--r--src/sage/rings/finite_rings/element_pari_ffelt.pyx2
-rw-r--r--src/sage/rings/integer.pyx2
-rw-r--r--src/sage/rings/number_field/totallyreal.pyx15
-rw-r--r--src/sage/rings/padics/common_conversion.pyx2
-rw-r--r--src/sage/rings/padics/padic_template_element.pxi2
-rw-r--r--src/sage/rings/rational.pyx4
-rw-r--r--src/sage/rings/real_mpfr.pyx2
-rw-r--r--src/sage/version.py4
97 files changed, 5957 insertions, 1474 deletions
diff --git a/VERSION.txt b/VERSION.txt
index c5542c0..c5de210 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-Sage version 6.8, released 2015-07-26
+Sage version 6.9.beta0, released 2015-07-29
diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini
index 3886688..e6e6098 100644
--- a/build/pkgs/configure/checksums.ini
+++ b/build/pkgs/configure/checksums.ini
@@ -1,4 +1,4 @@
tarball=configure-VERSION.tar.gz
-sha1=399df3d81aaaf47f0d7ed502378575a17fe8da55
-md5=037928df1bdcfde813640edd508a2757
-cksum=690617713
+sha1=74c12f15bd843d9bb723a17bac629846e4e8f57a
+md5=84df33fa9762cb8826ced5a7e6aadbc0
+cksum=4042526937
diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt
index f96ac06..fe4afb0 100644
--- a/build/pkgs/configure/package-version.txt
+++ b/build/pkgs/configure/package-version.txt
@@ -1 +1 @@
-105
+106
diff --git a/build/pkgs/freetype/checksums.ini b/build/pkgs/freetype/checksums.ini
index c5e8a4b..28929ba 100644
--- a/build/pkgs/freetype/checksums.ini
+++ b/build/pkgs/freetype/checksums.ini
@@ -1,4 +1,4 @@
tarball=freetype-VERSION.tar.bz2
-sha1=72731cf405b9f7c0b56d144130a8daafa262b729
-md5=10e8f4d6a019b124088d18bc26123a25
-cksum=1007257349
+sha1=cd2b144205dd2c61693e2d861069367aa3dde1bc
+md5=5682890cb0267f6671dd3de6eabd3e69
+cksum=4209260006
diff --git a/build/pkgs/freetype/package-version.txt b/build/pkgs/freetype/package-version.txt
index 5a8449e..5154b3f 100644
--- a/build/pkgs/freetype/package-version.txt
+++ b/build/pkgs/freetype/package-version.txt
@@ -1 +1 @@
-2.5.2.p1
+2.6
diff --git a/src/bin/sage b/src/bin/sage
index d5448d1..5ecb81b 100755
--- a/src/bin/sage
+++ b/src/bin/sage
@@ -206,9 +206,11 @@ usage_advanced() {
#### |.....................--.|...................................................|
echo "Making Sage packages or distributions:"
echo " -bdist <tmpdir> -- build a binary distribution of Sage"
+ echo " -sdist -- build a source distribution of Sage"
echo " -pkg <dir> -- create Sage package dir.spkg from a given directory"
echo " -pkg_nc <dir> -- as -pkg, but do not compress the package"
- echo " -sdist -- build a source distribution of Sage"
+ echo " -fix-pkg-checksums -- fix the checksums from build/pkgs directories from "
+ echo " the packages located in upstream/"
echo
#### 1.......................26..................................................78
@@ -459,6 +461,11 @@ if [ "$1" = '-pip' -o "$1" = '--pip' ]; then
exec pip "$@"
fi
+if [ "$1" = '-fix-pkg-checksums' -o "$1" = '--fix-pkg-checksums' ]; then
+ shift
+ exec sage-fix-pkg-checksums "$@"
+fi
+
if [ "$1" = '-python' -o "$1" = '--python' ]; then
shift
exec python "$@"
diff --git a/src/bin/sage-banner b/src/bin/sage-banner
index 85126bc..acc11a4 100644
--- a/src/bin/sage-banner
+++ b/src/bin/sage-banner
@@ -1,5 +1,8 @@
┌────────────────────────────────────────────────────────────────────┐
-│ SageMath Version 6.8, Release Date: 2015-07-26 │
+│ SageMath Version 6.9.beta0, Release Date: 2015-07-29 │
│ Type "notebook()" for the browser-based notebook interface. │
│ Type "help()" for help. │
└────────────────────────────────────────────────────────────────────┘
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Warning: this is a prerelease version, and it may be unstable. ┃
+┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
diff --git a/src/bin/sage-env b/src/bin/sage-env
index a865553..53246b1 100644
--- a/src/bin/sage-env
+++ b/src/bin/sage-env
@@ -377,8 +377,13 @@ if [ -d "$SAGE_LOCAL/lib/python" ]; then
PYTHONPATH="$SAGE_PATH:$PYTHONPATH"
fi
PYTHONHOME="$SAGE_LOCAL"
+ # Set PYTHONNOUSERSITE to avoid picking up non-Sage versions of
+ # Matplotlib, numpy, etc. See http://trac.sagemath.org/ticket/14243
+ # and http://trac.sagemath.org/ticket/18955.
+ PYTHONNOUSERSITE=yes
export PYTHONPATH
export PYTHONHOME
+ export PYTHONNOUSERSITE
fi
if [ -z "${SAGE_ORIG_LD_LIBRARY_PATH_SET}" ]; then
diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh
index ae9d2fb..493e910 100644
--- a/src/bin/sage-version.sh
+++ b/src/bin/sage-version.sh
@@ -1,4 +1,4 @@
# Sage version information for shell scripts
# This file is auto-generated by the sage-update-version script, do not edit!
-SAGE_VERSION='6.8'
-SAGE_RELEASE_DATE='2015-07-26'
+SAGE_VERSION='6.9.beta0'
+SAGE_RELEASE_DATE='2015-07-29'
diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst
index f7fb0ba..22e7397 100644
--- a/src/doc/en/reference/combinat/module_list.rst
+++ b/src/doc/en/reference/combinat/module_list.rst
@@ -61,6 +61,7 @@ Comprehensive Module list
sage/combinat/crystals/fast_crystals
sage/combinat/crystals/generalized_young_walls
sage/combinat/crystals/highest_weight_crystals
+ sage/combinat/crystals/induced_structure
sage/combinat/crystals/infinity_crystals
sage/combinat/crystals/polyhedral_realization
sage/combinat/crystals/kirillov_reshetikhin
diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst
index a605e05..6efcf30 100644
--- a/src/doc/en/reference/misc/index.rst
+++ b/src/doc/en/reference/misc/index.rst
@@ -293,6 +293,7 @@ Miscellaneous Inspection and Development Tools
sage/misc/dev_tools
sage/misc/function_mangling
sage/misc/memory_info
+ sage/misc/rest_index_of_methods
Low-Level Utilities
-------------------
diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py
index 8558e84..252f2e7 100644
--- a/src/sage/categories/crystals.py
+++ b/src/sage/categories/crystals.py
@@ -8,14 +8,20 @@ Crystals
# http://www.gnu.org/licenses/
#******************************************************************************
-from sage.misc.cachefunc import CachedFunction, cached_method
+from sage.misc.cachefunc import cached_method
from sage.misc.abstract_method import abstract_method
from sage.misc.lazy_import import LazyImport
from sage.categories.category_singleton import Category_singleton
from sage.categories.enumerated_sets import EnumeratedSets
from sage.categories.tensor import TensorProductsCategory
+from sage.categories.morphism import Morphism
+from sage.categories.homset import Hom, Homset
from sage.misc.latex import latex
+from sage.combinat import ranker
from sage.graphs.dot2tex_utils import have_dot2tex
+from sage.rings.integer import Integer
+from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet
+from sage.sets.family import Family
class Crystals(Category_singleton):
r"""
@@ -123,12 +129,96 @@ class Crystals(Category_singleton):
if choice == "naive":
return examples.NaiveCrystal(**kwds)
else:
- from sage.rings.integer import Integer
if isinstance(choice, Integer):
return examples.HighestWeightCrystalOfTypeA(n=choice, **kwds)
else:
return examples.HighestWeightCrystalOfTypeA(**kwds)
+ class MorphismMethods:
+ @cached_method
+ def is_isomorphism(self):
+ """
+ Check if ``self`` is a crystal isomorphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['C',2], shape=[1,1])
+ sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1]))
+ sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C)
+ sage: psi.is_isomorphism()
+ False
+ """
+ if self.domain().cardinality() != self.codomain().cardinality():
+ return False
+ if self.domain().cardinality() == float('inf'):
+ raise NotImplementedError("unable to determine if an isomorphism")
+
+ index_set = self._cartan_type.index_set()
+ G = self.domain().digraph(index_set=index_set)
+ if self.codomain().cardinality() != G.num_verts():
+ return False
+ H = self.codomain().digraph(index_set=index_set)
+ return G.is_isomorphic(H, edge_labels=True)
+
+ # TODO: This could be moved to sets
+ @cached_method
+ def is_embedding(self):
+ """
+ Check if ``self`` is an injective crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['C',2], shape=[1,1])
+ sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1]))
+ sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C)
+ sage: psi.is_embedding()
+ True
+
+ sage: C = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: B = crystals.infinity.Tableaux(['A',2])
+ sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights()
+ sage: W = crystals.elementary.T(['A',2], La[1]+La[2])
+ sage: T = W.tensor(B)
+ sage: mg = T(W.module_generators[0], B.module_generators[0])
+ sage: psi = Hom(C,T)([mg])
+ sage: psi.is_embedding()
+ True
+ """
+ if self.domain().cardinality() > self.codomain().cardinality():
+ return False
+ if self.domain().cardinality() == float('inf'):
+ raise NotImplementedError("unable to determine if an embedding")
+ S = set()
+ for x in self.domain():
+ y = self(x)
+ if y is None or y in S:
+ return False
+ S.add(y)
+ return True
+
+ @cached_method
+ def is_strict(self):
+ """
+ Check if ``self`` is a strict crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['C',2], shape=[1,1])
+ sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1]))
+ sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C)
+ sage: psi.is_strict()
+ True
+ """
+ if self.domain().cardinality() == float('inf'):
+ raise NotImplementedError("unable to determine if strict")
+ index_set = self._cartan_type.index_set()
+ for x in self.domain():
+ y = self(x)
+ if any(self(x.f(i)) != y.f(i) or self(x.e(i)) != y.e(i)
+ for i in index_set):
+ return False
+ return True
+
class ParentMethods:
def an_element(self):
@@ -209,10 +299,10 @@ class Crystals(Category_singleton):
INPUT:
- - ``index_set`` -- (Default: ``None``) The index set; if ``None``
+ - ``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
+ - ``max_depth`` -- (Default: infinity) the maximum depth to build
The iteration order is not specified except that, if
``max_depth`` is finite, then the iteration goes depth by
@@ -242,35 +332,50 @@ class Crystals(Category_singleton):
(Lambda[1] - Lambda[2],)]
"""
- from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet
if index_set is None:
index_set = self.index_set()
succ = lambda x: [x.f(i) for i in index_set] + [x.e(i) for i in index_set]
R = RecursivelyEnumeratedSet(self.module_generators, succ, structure=None)
return R.breadth_first_search_iterator(max_depth)
+
def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"),
- direction="both"):
+ direction="both", contained=None,
+ virtualization=None, scaling_factors=None,
+ cartan_type=None, category=None):
r"""
Construct the subcrystal from ``generators`` using `e_i` and/or
`f_i` for all `i` in ``index_set``.
INPUT:
- - ``index_set`` -- (Default: ``None``) The index set; if ``None``
+ - ``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
+ - ``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
+ - ``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`
+
+ - ``contained`` -- (optional) a set or function defining the
+ containment in the subcrystal
- - ``direction`` -- (Default: ``'both'``) The direction to build
- the subcrystal. It can be one of the following:
+ - ``virtualization``, ``scaling_factors`` -- (optional)
+ dictionaries whose key `i` corresponds to the sets `\sigma_i`
+ and `\gamma_i` respectively used to define virtual crystals; see
+ :class:`~sage.combinat.crystals.virtual_crystal.VirtualCrystal`
- - ``'both'`` - Using both `e_i` and `f_i`
- - ``'upper'`` - Using `e_i`
- - ``'lower'`` - Using `f_i`
+ - ``cartan_type`` -- (optional) specify the Cartan type of the
+ subcrystal
+
+ - ``category`` -- (optional) specify the category of the subcrystal
EXAMPLES::
@@ -289,13 +394,72 @@ class Crystals(Category_singleton):
[[[1, 4]], [[1, 3]]]
sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='lower'))
[[[1, 4]], [[2, 4]]]
+
+ sage: G = C.subcrystal(index_set=[1,2,3]).digraph()
+ sage: GA = crystals.Tableaux('A3', shape=[2]).digraph()
+ sage: G.is_isomorphic(GA, edge_labels=True)
+ True
+
+ We construct the subcrystal which contains the necessary data
+ to construct the corresponding dual equivalence graph::
+
+ sage: C = crystals.Tableaux(['A',5], shape=[3,3])
+ sage: is_wt0 = lambda x: all(x.epsilon(i) == x.phi(i) for i in x.parent().index_set())
+ sage: def check(x):
+ ....: if is_wt0(x):
+ ....: return True
+ ....: for i in x.parent().index_set()[:-1]:
+ ....: L = [x.e(i), x.e_string([i,i+1]), x.f(i), x.f_string([i,i+1])]
+ ....: if any(y is not None and is_wt0(y) for y in L):
+ ....: return True
+ ....: return False
+ sage: wt0 = [x for x in C if is_wt0(x)]
+ sage: S = C.subcrystal(contained=check, generators=wt0)
+ sage: S.module_generators[0]
+ [[1, 3, 5], [2, 4, 6]]
+ sage: S.module_generators[0].e(2).e(3).f(2).f(3)
+ [[1, 2, 5], [3, 4, 6]]
+
+ An example of a type `B_2` virtual crystal inside of a
+ type `A_3` ambient crystal::
+
+ sage: A = crystals.Tableaux(['A',3], shape=[2,1,1])
+ sage: S = A.subcrystal(virtualization={1:[1,3], 2:[2]},
+ ....: scaling_factors={1:1,2:1}, cartan_type=['B',2])
+ sage: B = crystals.Tableaux(['B',2], shape=[1])
+ sage: S.digraph().is_isomorphic(B.digraph(), edge_labels=True)
+ True
"""
+ from sage.combinat.crystals.subcrystal import Subcrystal
+ from sage.categories.finite_crystals import FiniteCrystals
+
+ if cartan_type is None:
+ cartan_type = self.cartan_type()
+ else:
+ from sage.combinat.root_system.cartan_type import CartanType
+ cartan_type = CartanType(cartan_type)
if index_set is None:
- index_set = self.index_set()
+ index_set = cartan_type.index_set()
if generators is None:
generators = self.module_generators
- from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet
+ if max_depth == float('inf'):
+ if self not in FiniteCrystals():
+ if (contained is None and index_set == self.index_set()
+ and generators == self.module_generators
+ and scaling_factors is None and virtualization is None):
+ return self
+ return Subcrystal(self, contained, generators,
+ virtualization, scaling_factors,
+ cartan_type, index_set, category)
+
+ if direction == 'both':
+ category = FiniteCrystals().or_subcategory(category)
+ return Subcrystal(self, contained, generators,
+ virtualization, scaling_factors,
+ cartan_type, index_set, category)
+
+ # TODO: Make this work for virtual crystals as well
if direction == 'both':
succ = lambda x: [x.f(i) for i in index_set] + [x.e(i) for i in index_set]
elif direction == 'upper':
@@ -304,154 +468,269 @@ class Crystals(Category_singleton):
succ = lambda x: [x.f(i) for i in index_set]
else:
raise ValueError("direction must be either 'both', 'upper', or 'lower'")
- return RecursivelyEnumeratedSet(generators, succ,
- structure=None, enumeration='breadth',
- max_depth=max_depth)
- 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):
+ subset = RecursivelyEnumeratedSet(generators, succ,
+ structure=None, enumeration='breadth',
+ max_depth=max_depth)
+
+ # We perform the filtering here since checking containment
+ # in a frozenset should be fast
+ if contained is not None:
+ try:
+ subset = frozenset(x for x in subset if x in contained)
+ except TypeError: # It does not have a containment test
+ subset = frozenset(x for x in subset if contained(x))
+ else:
+ subset = frozenset(subset)
+
+ if category is None:
+ category = FiniteCrystals()
+ else:
+ category = FiniteCrystals().join(category)
+
+ if self in FiniteCrystals() and len(subset) == self.cardinality():
+ if index_set == self.index_set():
+ return self
+
+ return Subcrystal(self, subset, generators,
+ virtualization, scaling_factors,
+ cartan_type, index_set, category)
+
+ def _Hom_(self, Y, category=None, **options):
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.
- Usually one would map highest weight elements or crystal generators to each
- other using g.
- Specifying index_set gives the opportunity to define the morphism as `I`-crystals
- where `I =` index_set. If index_set is not specified, the index set of self is used.
- It is also possible to define twisted-morphisms by specifying an automorphism on the
- nodes in te Dynkin diagram (or the index_set).
- The option direction and direction_image indicate whether to use `f_i` or `e_i` in
- self or the image crystal to construct the morphism, depending on whether the direction
- is set to 'down' or 'up'.
- It is also possible to set a similarity_factor. This should be a dictionary between
- the elements in the index set and positive integers. The crystal operator `f_i` then gets
- mapped to `f_i^{m_i}` where `m_i =` similarity_factor[i].
- Setting similarity_factor_domain to a dictionary between the index set and positive integers
- has the effect that `f_i^{m_i}` gets mapped to `f_i` where `m_i =` similarity_factor_domain[i].
- Finally, it is possible to set the option `acyclic = False`. This calculates an isomorphism
- for cyclic crystals (for example finite affine crystals). In this case the input function `g`
- is supposed to be given as a dictionary.
+ Return the homset from ``self`` to ``Y`` in the
+ category ``category``.
- EXAMPLES::
+ INPUT:
- sage: C2 = crystals.Letters(['A',2])
- sage: C3 = crystals.Letters(['A',3])
- sage: g = {C2.module_generators[0] : C3.module_generators[0]}
- sage: g_full = C2.crystal_morphism(g)
- sage: g_full(C2(1))
- 1
- sage: g_full(C2(2))
- 2
- sage: g = {C2(1) : C2(3)}
- sage: g_full = C2.crystal_morphism(g, automorphism = lambda i : 3-i, direction_image = 'up')
- sage: [g_full(b) for b in C2]
- [3, 2, 1]
- sage: T = crystals.Tableaux(['A',2], shape = [2])
- sage: g = {C2(1) : T(rows=[[1,1]])}
- sage: g_full = C2.crystal_morphism(g, similarity_factor = {1:2, 2:2})
- sage: [g_full(b) for b in C2]
- [[[1, 1]], [[2, 2]], [[3, 3]]]
- sage: g = {T(rows=[[1,1]]) : C2(1)}
- sage: g_full = T.crystal_morphism(g, similarity_factor_domain = {1:2, 2:2})
- sage: g_full(T(rows=[[2,2]]))
- 2
+ - ``Y`` -- a crystal
+ - ``category`` -- a subcategory of :class:`Crystals`() or ``None``
+
+ The sole purpose of this method is to construct the homset
+ as a :class:`~sage.categories.crystals.CrystalHomset`. If
+ ``category`` is specified and is not a subcategory of
+ :class:`Crystals`, a ``TypeError`` is raised instead.
+
+ This method is not meant to be called directly. Please use
+ :func:`sage.categories.homset.Hom` instead.
- sage: B1 = crystals.KirillovReshetikhin(['A',2,1],1,1)
- sage: B2 = crystals.KirillovReshetikhin(['A',2,1],1,2)
- sage: T = crystals.TensorProduct(B1,B2)
- sage: T1 = crystals.TensorProduct(B2,B1)
- sage: La = T.weight_lattice_realization().fundamental_weights()
- sage: t = [b for b in T if b.weight() == -3*La[0] + 3*La[1]][0]
- sage: t1 = [b for b in T1 if b.weight() == -3*La[0] + 3*La[1]][0]
- sage: g={t:t1}
- sage: f=T.crystal_morphism(g,acyclic = False)
- sage: [[b,f(b)] for b in T]
- [[[[[1]], [[1, 1]]], [[[1, 1]], [[1]]]],
- [[[[1]], [[1, 2]]], [[[1, 1]], [[2]]]],
- [[[[1]], [[2, 2]]], [[[1, 2]], [[2]]]],
- [[[[1]], [[1, 3]]], [[[1, 1]], [[3]]]],
- [[[[1]], [[2, 3]]], [[[1, 2]], [[3]]]],
- [[[[1]], [[3, 3]]], [[[1, 3]], [[3]]]],
- [[[[2]], [[1, 1]]], [[[1, 2]], [[1]]]],
- [[[[2]], [[1, 2]]], [[[2, 2]], [[1]]]],
- [[[[2]], [[2, 2]]], [[[2, 2]], [[2]]]],
- [[[[2]], [[1, 3]]], [[[2, 3]], [[1]]]],
- [[[[2]], [[2, 3]]], [[[2, 2]], [[3]]]],
- [[[[2]], [[3, 3]]], [[[2, 3]], [[3]]]],
- [[[[3]], [[1, 1]]], [[[1, 3]], [[1]]]],
- [[[[3]], [[1, 2]]], [[[1, 3]], [[2]]]],
- [[[[3]], [[2, 2]]], [[[2, 3]], [[2]]]],
- [[[[3]], [[1, 3]]], [[[3, 3]], [[1]]]],
- [[[[3]], [[2, 3]]], [[[3, 3]], [[2]]]],
- [[[[3]], [[3, 3]]], [[[3, 3]], [[3]]]]]
+ EXAMPLES::
+
+ sage: B = crystals.elementary.B(['A',2], 1)
+ sage: H = B._Hom_(B); H
+ Set of Crystal Morphisms from The 1-elementary crystal of type ['A', 2]
+ to The 1-elementary crystal of type ['A', 2]
"""
- if index_set is None:
- index_set = self.index_set()
- if similarity_factor is None:
- similarity_factor = dict( (i,1) for i in index_set )
- if similarity_factor_domain is None:
- similarity_factor_domain = dict( (i,1) for i in index_set )
- if direction == 'down':
- e_string = 'e_string'
- else:
- e_string = 'f_string'
- if direction_image == 'down':
- f_string = 'f_string'
- else:
- f_string = 'e_string'
-
- if acyclic:
- if isinstance(g, dict):
- g = g.__getitem__
-
- def morphism(b):
- for i in index_set:
- c = getattr(b, e_string)([i for k in range(similarity_factor_domain[i])])
- if c is not None:
- d = getattr(morphism(c), f_string)([automorphism(i) for k in range(similarity_factor[i])])
- if d is not None:
- return d
- else:
- raise ValueError("This is not a morphism!")
- #now we know that b is hw
- return g(b)
+ if category is None:
+ category = self.category()
+ elif not category.is_subcategory(Crystals()):
+ raise TypeError("{} is not a subcategory of Crystals()".format(category))
+ if Y not in Crystals():
+ raise TypeError("{} is not a crystal".format(Y))
+ return CrystalHomset(self, Y, category=category, **options)
+
+ def crystal_morphism(self, on_gens, codomain=None,
+ cartan_type=None, index_set=None, generators=None,
+ automorphism=None,
+ virtualization=None, scaling_factors=None,
+ category=None, check=True):
+ r"""
+ Construct a crystal morphism from ``self`` to another crystal
+ ``codomain``.
- else:
- import copy
- morphism = copy.copy(g)
- known = set( g.keys() )
- todo = copy.copy(known)
- images = set( [g[x] for x in known] )
- # Invariants:
- # - images contains all known morphism(x)
- # - known contains all elements x for which we know morphism(x)
- # - todo contains all elements x for which we haven't propagated to each child
- while todo != set( [] ):
- x = todo.pop()
- for i in index_set:
- eix = getattr(x, f_string)([i for k in range(similarity_factor_domain[i])])
- eigx = getattr(morphism[x], f_string)([automorphism(i) for k in range(similarity_factor[i])])
- if bool(eix is None) != bool(eigx is None):
- # This is not a crystal morphism!
- raise ValueError("This is not a morphism!") #, print("x="x,"g(x)="g(x),"i="i)
- if (eix is not None) and (eix not in known):
- todo.add(eix)
- known.add(eix)
- morphism[eix] = eigx
- images.add(eigx)
- # Check that the module generators are indeed module generators
- assert(len(known) == self.cardinality())
- # We may not want to systematically run those tests,
- # to allow for non bijective crystal morphism
- # Add an option CheckBijective?
- if not ( len(known) == len(images) and len(images) == images.pop().parent().cardinality() ):
- return(None)
- return morphism.__getitem__
-
- if cached:
- return morphism
- else:
- return CachedFunction(morphism)
+ INPUT:
+
+ - ``on_gens`` -- a function or list that determines the image
+ of the generators (if given a list, then this uses the order
+ of the generators of the domain) of ``self`` under the
+ crystal morphism
+ - ``codomain`` -- (default: ``self``) the codomain of the morphism
+ - ``cartan_type`` -- (optional) the Cartan type of the morphism;
+ the default is the Cartan type of ``self``
+ - ``index_set`` -- (optional) the index set of the morphism;
+ the default is the index set of the Cartan type
+ - ``generators`` -- (optional) the generators to define the
+ morphism; the default is the generators of ``self``
+ - ``automorphism`` -- (optional) the automorphism to perform the
+ twisting
+ - ``virtualization`` -- (optional) a dictionary whose keys are
+ in the index set of the domain and whose values are lists of
+ entries in the index set of the codomain; the default is the
+ identity dictionary
+ - ``scaling_factors`` -- (optional) a dictionary whose keys are
+ in the index set of the domain and whose values are scaling
+ factors for the weight, `\varepsilon` and `\varphi`; the
+ default are all scaling factors to be one
+ - ``category`` -- (optional) the category for the crystal morphism;
+ the default is the category of :class:`Crystals`.
+ - ``check`` -- (default: ``True``) check if the crystal morphism
+ is valid
+
+ .. SEEALSO::
+
+ For more examples, see
+ :class:`sage.categories.crystals.CrystalHomset`.
+
+ EXAMPLES:
+
+ We construct the natural embedding of a crystal using tableaux
+ into the tensor product of single boxes via the reading word::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: F = crystals.Tableaux(['A',2], shape=[1])
+ sage: T = crystals.TensorProduct(F, F, F)
+ sage: mg = T.highest_weight_vectors()[2]; mg
+ [[[1]], [[2]], [[1]]]
+ sage: psi = B.crystal_morphism([mg], codomain=T); psi
+ ['A', 2] Crystal morphism:
+ From: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ To: Full tensor product of the crystals
+ [The crystal of tableaux of type ['A', 2] and shape(s) [[1]],
+ The crystal of tableaux of type ['A', 2] and shape(s) [[1]],
+ The crystal of tableaux of type ['A', 2] and shape(s) [[1]]]
+ Defn: [2, 1, 1] |--> [[[1]], [[2]], [[1]]]
+ sage: b = B.module_generators[0]
+ sage: b.pp()
+ 1 1
+ 2
+ sage: psi(b)
+ [[[1]], [[2]], [[1]]]
+ sage: psi(b.f(2))
+ [[[1]], [[3]], [[1]]]
+ sage: psi(b.f_string([2,1,1]))
+ [[[2]], [[3]], [[2]]]
+ sage: lw = b.to_lowest_weight()[0]
+ sage: lw.pp()
+ 2 3
+ 3
+ sage: psi(lw)
+ [[[3]], [[3]], [[2]]]
+ sage: psi(lw) == mg.to_lowest_weight()[0]
+ True
+
+ We now take the other isomorphic highest weight component
+ in the tensor product::
+
+ sage: mg = T.highest_weight_vectors()[1]; mg
+ [[[2]], [[1]], [[1]]]
+ sage: psi = B.crystal_morphism([mg], codomain=T)
+ sage: psi(lw)
+ [[[3]], [[2]], [[3]]]
+
+ We construct a crystal morphism of classical crystals using a
+ Kirillov-Reshetikhin crystal::
+
+ sage: B = crystals.Tableaux(['D', 4], shape=[1,1])
+ sage: K = crystals.KirillovReshetikhin(['D',4,1], 2,2)
+ sage: K.module_generators
+ [[], [[1], [2]], [[1, 1], [2, 2]]]
+ sage: v = K.module_generators[1]
+ sage: psi = B.crystal_morphism([v], codomain=K, category=FiniteCrystals())
+ sage: psi
+ ['D', 4] -> ['D', 4, 1] Virtual Crystal morphism:
+ From: The crystal of tableaux of type ['D', 4] and shape(s) [[1, 1]]
+ To: Kirillov-Reshetikhin crystal of type ['D', 4, 1] with (r,s)=(2,2)
+ Defn: [2, 1] |--> [[1], [2]]
+ sage: b = B.module_generators[0]
+ sage: psi(b)
+ [[1], [2]]
+ sage: psi(b.to_lowest_weight()[0])
+ [[-2], [-1]]
+
+ We can define crystal morphisms using a different set of
+ generators. For example, we construct an example using the
+ lowest weight vector::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[1])
+ sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights()
+ sage: T = crystals.elementary.T(['A',2], La[2])
+ sage: Bp = T.tensor(B)
+ sage: C = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: x = C.module_generators[0].f_string([1,2])
+ sage: psi = Bp.crystal_morphism([x], generators=Bp.lowest_weight_vectors())
+ sage: psi(Bp.highest_weight_vector())
+ [[1, 1], [2]]
+
+ We can also use a dictonary to specify the generators and
+ their images::
+
+ sage: psi = Bp.crystal_morphism({Bp.lowest_weight_vectors()[0]: x})
+ sage: psi(Bp.highest_weight_vector())
+ [[1, 1], [2]]
+
+ We construct a twisted crystal morphism induced from the diagram
+ automorphism of type `A_3^{(1)}`::
+
+ sage: La = RootSystem(['A',3,1]).weight_lattice(extended=True).fundamental_weights()
+ sage: B0 = crystals.GeneralizedYoungWalls(3, La[0])
+ sage: B1 = crystals.GeneralizedYoungWalls(3, La[1])
+ sage: phi = B0.crystal_morphism(B1.module_generators, automorphism={0:1, 1:2, 2:3, 3:0})
+ sage: phi
+ ['A', 3, 1] Twisted Crystal morphism:
+ From: Highest weight crystal of generalized Young walls of Cartan type ['A', 3, 1] and highest weight Lambda[0]
+ To: Highest weight crystal of generalized Young walls of Cartan type ['A', 3, 1] and highest weight Lambda[1]
+ Defn: [] |--> []
+ sage: x = B0.module_generators[0].f_string([0,1,2,3]); x
+ [[0, 3], [1], [2]]
+ sage: phi(x)
+ [[], [1, 0], [2], [3]]
+
+ We construct a virtual crystal morphism from type `G_2` into
+ type `D_4`::
+
+ sage: D = crystals.Tableaux(['D',4], shape=[1,1])
+ sage: G = crystals.Tableaux(['G',2], shape=[1])
+ sage: psi = G.crystal_morphism(D.module_generators,
+ ....: virtualization={1:[2],2:[1,3,4]},
+ ....: scaling_factors={1:1, 2:1})
+ sage: for x in G:
+ ....: ascii_art(x, psi(x), sep=' |--> ')
+ ....: print ""
+ 1
+ 1 |--> 2
+ <BLANKLINE>
+ 1
+ 2 |--> 3
+ <BLANKLINE>
+ 2
+ 3 |--> -3
+ <BLANKLINE>
+ 3
+ 0 |--> -3
+ <BLANKLINE>
+ 3
+ -3 |--> -2
+ <BLANKLINE>
+ -3
+ -2 |--> -1
+ <BLANKLINE>
+ -2
+ -1 |--> -1
+ """
+ # Determine the codomain
+ if codomain is None:
+ if hasattr(on_gens, 'codomain'):
+ codomain = on_gens.codomain()
+ elif isinstance(on_gens, (list, tuple)):
+ if on_gens:
+ codomain = on_gens[0].parent()
+ elif isinstance(on_gens, dict):
+ if on_gens:
+ codomain = on_gens.values()[0].parent()
+ else:
+ for x in self.module_generators:
+ y = on_gens(x)
+ if y is not None:
+ codomain = y.parent()
+ break
+ if codomain is None:
+ codomain = self
+ elif codomain not in Crystals():
+ raise ValueError("the codomain must be a crystal")
+
+ homset = Hom(self, codomain, category=category)
+ return homset(on_gens, cartan_type, index_set, generators,
+ automorphism, virtualization, scaling_factors, check)
def digraph(self, subset=None, index_set=None):
"""
@@ -459,10 +738,10 @@ class Crystals(Category_singleton):
INPUT:
- - ``subset`` -- (Optional) A subset of vertices for
+ - ``subset`` -- (optional) a subset of vertices for
which the digraph should be constructed
- - ``index_set`` -- (Optional) The index set to draw arrows
+ - ``index_set`` -- (optional) the index set to draw arrows
EXAMPLES::
@@ -470,29 +749,29 @@ class Crystals(Category_singleton):
sage: C.digraph()
Digraph on 6 vertices
- The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2,
- and green for edge 3::
+ The edges of the crystal graph are by default colored using
+ blue for edge 1, red for edge 2, and green for edge 3::
sage: C = Crystals().example(3)
sage: G = C.digraph()
- sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
+ sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
One may also overwrite the colors::
sage: C = Crystals().example(3)
sage: G = C.digraph()
sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"})
- sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
+ sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
Or one may add colors to yet unspecified edges::
sage: C = Crystals().example(4)
sage: G = C.digraph()
sage: C.cartan_type()._index_set_coloring[4]="purple"
- sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
+ sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
- Here is an example of how to take the top part up to a given depth of an infinite dimensional
- crystal::
+ 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()
@@ -511,15 +790,12 @@ class Crystals(Category_singleton):
the ``subset`` option::
sage: B = crystals.Tableaux(['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], [2]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 1], [3]]] + B[[[1, 3], [3]]]
- 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, not tested (opens external window)
+ sage: D = B.demazure_subcrystal(t, [2,1])
+ sage: list(D)
+ [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]],
+ [[1, 3], [2]], [[1, 3], [3]]]
+ sage: view(D, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
We can also choose to display particular arrows using the
``index_set`` option::
@@ -528,9 +804,9 @@ class Crystals(Category_singleton):
sage: G = C.digraph(index_set=[1,3])
sage: len(G.edges())
20
- sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
+ sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window)
- TODO: add more tests
+ .. TODO:: Add more tests.
"""
from sage.graphs.all import DiGraph
from sage.categories.highest_weight_crystals import HighestWeightCrystals
@@ -602,7 +878,7 @@ class Crystals(Category_singleton):
sage: T = crystals.Tableaux(['A',2],shape=[1])
sage: T._latex_() # optional - dot2tex graphviz
'...tikzpicture...'
- sage: view(T, pdflatex = True, tightpage = True) # optional - dot2tex graphviz, not tested (opens external window)
+ sage: view(T, tightpage = True) # optional - dot2tex graphviz, not tested (opens external window)
One can for example also color the edges using the following options::
@@ -758,7 +1034,7 @@ class Crystals(Category_singleton):
def dot_tex(self):
r"""
- Returns a dot_tex string representation of ``self``.
+ Return a dot_tex string representation of ``self``.
EXAMPLES::
@@ -767,7 +1043,6 @@ class Crystals(Category_singleton):
'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}'
"""
import re
- from sage.combinat import ranker
rank = ranker.from_list(self.list())[0]
vertex_key = lambda x: "N_"+str(rank(x))
@@ -798,7 +1073,7 @@ class Crystals(Category_singleton):
def plot(self, **options):
"""
- Returns the plot of self as a directed graph.
+ Return the plot of ``self`` as a directed graph.
EXAMPLES::
@@ -810,7 +1085,7 @@ class Crystals(Category_singleton):
def plot3d(self, **options):
"""
- Returns the 3-dimensional plot of self as a directed graph.
+ Return the 3-dimensional plot of ``self`` as a directed graph.
EXAMPLES::
@@ -849,6 +1124,101 @@ class Crystals(Category_singleton):
from sage.combinat.crystals.tensor_product import TensorProductOfCrystals
return TensorProductOfCrystals(self, *crystals, **options)
+ def direct_sum(self, X):
+ """
+ Return the direct sum of ``self`` with ``X``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: C = crystals.Letters(['A',2])
+ sage: B.direct_sum(C)
+ Direct sum of the crystals Family
+ (The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]],
+ The crystal of letters for type ['A', 2])
+
+ As a shorthand, we can use ``+``::
+
+ sage: B + C
+ Direct sum of the crystals Family
+ (The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]],
+ The crystal of letters for type ['A', 2])
+ """
+ if X not in Crystals():
+ raise ValueError("{} is not a crystal".format(X))
+ from sage.combinat.crystals.direct_sum import DirectSumOfCrystals
+ return DirectSumOfCrystals([self, X])
+
+ __add__ = direct_sum
+
+ @abstract_method(optional=True)
+ def connected_components_generators(self):
+ """
+ Return a tuple of generators for each of the connected components
+ of ``self``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: C = crystals.Letters(['A',2])
+ sage: T = crystals.TensorProduct(B,C)
+ sage: T.connected_components_generators()
+ ([[[1, 1], [2]], 1], [[[1, 2], [2]], 1], [[[1, 2], [3]], 1])
+ """
+
+ def connected_components(self):
+ """
+ Return the connected components of ``self`` as subcrystals.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: C = crystals.Letters(['A',2])
+ sage: T = crystals.TensorProduct(B,C)
+ sage: T.connected_components()
+ [Subcrystal of Full tensor product of the crystals
+ [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]],
+ The crystal of letters for type ['A', 2]],
+ Subcrystal of Full tensor product of the crystals
+ [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]],
+ The crystal of letters for type ['A', 2]],
+ Subcrystal of Full tensor product of the crystals
+ [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]],
+ The crystal of letters for type ['A', 2]]]
+ """
+ return [self.subcrystal(generators=[mg])
+ for mg in self.connected_components_generators()]
+
+ def number_of_connected_components(self):
+ """
+ Return the number of connected components of ``self``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: C = crystals.Letters(['A',2])
+ sage: T = crystals.TensorProduct(B,C)
+ sage: T.number_of_connected_components()
+ 3
+ """
+ return len(self.connected_components_generators())
+
+ def is_connected(self):
+ """
+ Return ``True`` if ``self`` is a connected crystal.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: C = crystals.Letters(['A',2])
+ sage: T = crystals.TensorProduct(B,C)
+ sage: B.is_connected()
+ True
+ sage: T.is_connected()
+ False
+ """
+ return self.number_of_connected_components() == 1
+
class ElementMethods:
@cached_method
@@ -877,7 +1247,7 @@ class Crystals(Category_singleton):
@abstract_method
def e(self, i):
r"""
- Returns `e_i(x)` if it exists or ``None`` otherwise.
+ Return `e_i` of ``self`` if it exists or ``None`` otherwise.
This method should be implemented by the element class of
the crystal.
@@ -894,7 +1264,7 @@ class Crystals(Category_singleton):
@abstract_method
def f(self, i):
r"""
- Returns `f_i(x)` if it exists or ``None`` otherwise.
+ Return `f_i` of ``self`` if it exists or ``None`` otherwise.
This method should be implemented by the element class of
the crystal.
@@ -994,7 +1364,8 @@ class Crystals(Category_singleton):
def f_string(self, list):
r"""
- Applies `f_{i_r} ... f_{i_1}` to self for `list = [i_1, ..., i_r]`
+ Applies `f_{i_r} \cdots f_{i_1}` to self for ``list`` as
+ `[i_1, ..., i_r]`
EXAMPLES::
@@ -1013,7 +1384,8 @@ class Crystals(Category_singleton):
def e_string(self, list):
r"""
- Applies `e_{i_r} ... e_{i_1}` to self for `list = [i_1, ..., i_r]`
+ Applies `e_{i_r} \cdots e_{i_1}` to self for ``list`` as
+ `[i_1, ..., i_r]`
EXAMPLES::
@@ -1032,19 +1404,19 @@ class Crystals(Category_singleton):
def s(self, i):
r"""
- Returns the reflection of ``self`` along its `i`-string
+ Return the reflection of ``self`` along its `i`-string.
EXAMPLES::
sage: C = crystals.Tableaux(['A',2], shape=[2,1])
- sage: b=C(rows=[[1,1],[3]])
+ sage: b = C(rows=[[1,1],[3]])
sage: b.s(1)
[[2, 2], [3]]
- sage: b=C(rows=[[1,2],[3]])
+ sage: b = C(rows=[[1,2],[3]])
sage: b.s(2)
[[1, 2], [3]]
- sage: T=crystals.Tableaux(['A',2],shape=[4])
- sage: t=T(rows=[[1,2,2,2]])
+ sage: T = crystals.Tableaux(['A',2],shape=[4])
+ sage: t = T(rows=[[1,2,2,2]])
sage: t.s(1)
[[1, 1, 1, 2]]
"""
@@ -1226,28 +1598,37 @@ class Crystals(Category_singleton):
if hw:
yield []
- def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both"):
+ def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both",
+ contained=None, cartan_type=None, category=None):
r"""
Construct the subcrystal generated by ``self`` using `e_i` and/or
`f_i` for all `i` in ``index_set``.
INPUT:
- - ``index_set`` -- (Default: ``None``) The index set; if ``None``
+ - ``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
+ - ``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`
+
+ - ``contained`` -- (optional) a set (or function) defining the
+ containment in the subcrystal
- - ``direction`` -- (Default: ``'both'``) The direction to build
- the subcrystal. It can be one of the following:
+ - ``cartan_type`` -- (optional) specify the Cartan type of the
+ subcrystal
- - ``'both'`` - Using both `e_i` and `f_i`
- - ``'upper'`` - Using `e_i`
- - ``'lower'`` - Using `f_i`
+ - ``category`` -- (optional) specify the category of the subcrystal
.. SEEALSO::
- - :meth:`Crystals.ParentMethods.subcrystal()`.
+ - :meth:`Crystals.ParentMethods.subcrystal()`
EXAMPLES::
@@ -1302,3 +1683,806 @@ class Crystals(Category_singleton):
Finite = LazyImport('sage.categories.finite_crystals', 'FiniteCrystals')
+###############################################################################
+## Morphisms
+
+class CrystalMorphism(Morphism):
+ r"""
+ A crystal morphism.
+
+ INPUT:
+
+ - ``parent`` -- a homset
+ - ``cartan_type`` -- (optional) a Cartan type; the default is the
+ Cartan type of the domain
+ - ``virtualization`` -- (optional) a dictionary whose keys are in
+ the index set of the domain and whose values are lists of entries
+ in the index set of the codomain
+ - ``scaling_factors`` -- (optional) a dictionary whose keys are in
+ the index set of the domain and whose values are scaling factors
+ for the weight, `\varepsilon` and `\varphi`
+ """
+ def __init__(self, parent, cartan_type=None,
+ virtualization=None, scaling_factors=None):
+ """
+ Initialize ``self``.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: psi = H.an_element()
+ """
+ if cartan_type is None:
+ cartan_type = parent.domain().cartan_type()
+ self._cartan_type = cartan_type
+
+ index_set = cartan_type.index_set()
+ if scaling_factors is None:
+ scaling_factors = {i: 1 for i in index_set}
+ if virtualization is None:
+ virtualization = {i: (i,) for i in index_set}
+ elif not isinstance(virtualization, dict):
+ try:
+ virtualization = dict(virtualization)
+ except (TypeError, ValueError):
+ virtualization = {i: (virtualization(i),) for i in index_set}
+ self._virtualization = Family(virtualization)
+ self._scaling_factors = Family(scaling_factors)
+
+ Morphism.__init__(self, parent)
+
+ def _repr_type(self):
+ """
+ Used internally in printing this morphism.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: psi = H.an_element()
+ sage: psi._repr_type()
+ "['A', 2] Crystal"
+
+ sage: psi = H(lambda x: None, index_set=[1])
+ sage: psi._repr_type()
+ "['A', 1] -> ['A', 2] Virtual Crystal"
+
+ sage: B = crystals.Tableaux(['A',3], shape=[1])
+ sage: BT = crystals.Tableaux(['A',3], shape=[1,1,1])
+ sage: psi = B.crystal_morphism(BT.module_generators, automorphism={1:3, 2:2, 3:1})
+ sage: psi._repr_type()
+ "['A', 3] Twisted Crystal"
+
+ sage: KD = crystals.KirillovReshetikhin(['D',3,1], 2,1)
+ sage: KA = crystals.KirillovReshetikhin(['A',3,1], 2,1)
+ sage: psi = KD.crystal_morphism(KA.module_generators)
+ sage: psi._repr_type()
+ "['D', 3, 1] -> ['A', 3, 1] Virtual Crystal"
+ """
+ if self.codomain().cartan_type() != self._cartan_type:
+ return "{} -> {} Virtual Crystal".format(self._cartan_type, self.codomain().cartan_type())
+ if any(self._virtualization[i] != (i,) for i in self._cartan_type.index_set()):
+ return "{} Twisted Crystal".format(self._cartan_type)
+ return "{} Crystal".format(self._cartan_type)
+
+ def cartan_type(self):
+ """
+ Return the Cartan type of ``self``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: psi = Hom(B, B).an_element()
+ sage: psi.cartan_type()
+ ['A', 2]
+ """
+ return self._cartan_type
+
+ # This is needed because is_injective is defined in a superclass, so
+ # we can't overwrite it with the category
+ def is_injective(self):
+ """
+ Return if ``self`` is an injective crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: psi = Hom(B, B).an_element()
+ sage: psi.is_injective()
+ False
+ """
+ return self.is_embedding()
+
+ # This is here because is_surjective is defined in a superclass, so
+ # we can't overwrite it with the category
+ # TODO: This could be moved to sets
+ @cached_method
+ def is_surjective(self):
+ """
+ Check if ``self`` is a surjective crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['C',2], shape=[1,1])
+ sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1]))
+ sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C)
+ sage: psi.is_surjective()
+ False
+ sage: im_gens = [None, B.module_generators[0]]
+ sage: psi = C.crystal_morphism(im_gens, codomain=B)
+ sage: psi.is_surjective()
+ True
+
+ sage: C = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: B = crystals.infinity.Tableaux(['A',2])
+ sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights()
+ sage: W = crystals.elementary.T(['A',2], La[1]+La[2])
+ sage: T = W.tensor(B)
+ sage: mg = T(W.module_generators[0], B.module_generators[0])
+ sage: psi = Hom(C,T)([mg])
+ sage: psi.is_surjective()
+ False
+ """
+ if self.domain().cardinality() == float('inf'):
+ raise NotImplementedError("unable to determine if surjective")
+ if self.domain().cardinality() < self.codomain().cardinality():
+ return False
+ S = set(self.codomain())
+ for x in self.domain():
+ S.discard(self(x))
+ if not S:
+ return True
+ return False
+
+ def __call__(self, x, *args, **kwds):
+ """
+ Apply this map to ``x``. We need to do special processing
+ for ``None``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: F = crystals.Tableaux(['A',2], shape=[1])
+ sage: T = crystals.TensorProduct(F, F, F)
+ sage: H = Hom(T, B)
+ sage: b = B.module_generators[0]
+ sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors())
+ sage: psi(None)
+ sage: [psi(v) for v in T.highest_weight_vectors()]
+ [None, [[1, 1], [2]], [[1, 1], [2]], None]
+ """
+ if x is None:
+ return None
+ return super(CrystalMorphism, self).__call__(x, *args, **kwds)
+
+ def virtualization(self):
+ """
+ Return the virtualization sets `\sigma_i`.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: psi = B.crystal_morphism(C.module_generators)
+ sage: psi.virtualization()
+ Finite family {1: (1,), 2: (2,), 3: (3, 4)}
+ """
+ return self._virtualization
+
+ def scaling_factors(self):
+ """
+ Return the scaling factors `\gamma_i`.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: psi = B.crystal_morphism(C.module_generators)
+ sage: psi.scaling_factors()
+ Finite family {1: 2, 2: 2, 3: 1}
+ """
+ return self._scaling_factors
+
+class CrystalMorphismByGenerators(CrystalMorphism):
+ r"""
+ A crystal morphism defined by a set of generators which create a virtual
+ crystal inside the codomain.
+
+ INPUT:
+
+ - ``parent`` -- a homset
+ - ``on_gens`` -- a function or list that determines the image of the
+ generators (if given a list, then this uses the order of the
+ generators of the domain) of the domain under ``self``
+ - ``cartan_type`` -- (optional) a Cartan type; the default is the
+ Cartan type of the domain
+ - ``virtualization`` -- (optional) a dictionary whose keys are in
+ the index set of the domain and whose values are lists of entries
+ in the index set of the codomain
+ - ``scaling_factors`` -- (optional) a dictionary whose keys are in
+ the index set of the domain and whose values are scaling factors
+ for the weight, `\varepsilon` and `\varphi`
+ - ``gens`` -- (optional) a finite list of generators to define the
+ morphism; the default is to use the highest weight vectors of the crystal
+ - ``check`` -- (default: ``True``) check if the crystal morphism is valid
+
+ .. SEEALSO::
+
+ :meth:`sage.categories.crystals.Crystals.ParentMethods.crystal_morphism`
+ """
+ def __init__(self, parent, on_gens, cartan_type=None,
+ virtualization=None, scaling_factors=None,
+ gens=None, check=True):
+ """
+ Construct a virtual crystal morphism.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['D',4], shape=[1])
+ sage: H = Hom(B, B)
+ sage: d = {1:1, 2:2, 3:4, 4:3}
+ sage: psi = H(B.module_generators, automorphism=d)
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ """
+ CrystalMorphism.__init__(self, parent, cartan_type,
+ virtualization, scaling_factors)
+
+ if gens is None:
+ if isinstance(on_gens, dict):
+ gens = on_gens.keys()
+ else:
+ gens = parent.domain().module_generators
+ self._gens = tuple(gens)
+
+ # Make sure on_gens is a function
+ if isinstance(on_gens, dict):
+ f = lambda x: on_gens[x]
+ elif isinstance(on_gens, (list, tuple)):
+ if len(self._gens) != len(on_gens):
+ raise ValueError("invalid generator images")
+ d = {x: y for x,y in zip(self._gens, on_gens)}
+ f = lambda x: d[x]
+ else:
+ f = on_gens
+ self._on_gens = f
+ self._path_mg_cache = {x: (x, [], []) for x in self._gens}
+
+ # Now that everything is initialized, run the check (if it is wanted)
+ if check:
+ self._check()
+
+ def _repr_defn(self):
+ """
+ Used in constructing string representation of ``self``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: F = crystals.Tableaux(['A',2], shape=[1])
+ sage: T = crystals.TensorProduct(F, F, F)
+ sage: H = Hom(T, B)
+ sage: b = B.highest_weight_vector()
+ sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors())
+ sage: print psi._repr_defn()
+ [[[1]], [[1]], [[1]]] |--> None
+ [[[2]], [[1]], [[1]]] |--> [2, 1, 1]
+ [[[1]], [[2]], [[1]]] |--> [2, 1, 1]
+ [[[3]], [[2]], [[1]]] |--> None
+ """
+ return '\n'.join(['{} |--> {}'.format(mg, im)
+ for mg,im in zip(self._gens, self.im_gens())])
+
+ def _check(self):
+ """
+ Check if ``self`` is a valid virtual crystal morphism.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['D',4], shape=[1])
+ sage: H = Hom(B, B)
+ sage: d = {1:1, 2:2, 3:4, 4:3}
+ sage: psi = H(B.module_generators, automorphism=d) # indirect doctest
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators) # indirect doctest
+ """
+ index_set = self._cartan_type.index_set()
+ acx = self.domain().weight_lattice_realization().simple_coroots()
+ acy = self.codomain().weight_lattice_realization().simple_coroots()
+ v = self._virtualization
+ sf = self._scaling_factors
+ for x in self._gens:
+ y = self._on_gens(x)
+ if y is None:
+ continue
+ xwt = x.weight()
+ ywt = y.weight()
+ for i in index_set:
+ ind = v[i]
+ if any(sf[i] * xwt.scalar(acx[i]) != ywt.scalar(acy[j]) for j in ind):
+ raise ValueError("invalid crystal morphism: weights do not match")
+ if any(sf[i] * x.epsilon(i) != y.epsilon(j) for j in ind):
+ raise ValueError("invalid crystal morphism: epsilons are not aligned")
+ if any(sf[i] * x.phi(i) != y.phi(j) for j in ind):
+ raise ValueError("invalid crystal morphism: phis are not aligned")
+
+ def _call_(self, x):
+ """
+ Return the image of ``x`` under ``self``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: psi = H(B.module_generators)
+ sage: psi(B.highest_weight_vector())
+ [[1, 1], [2]]
+
+ sage: B = crystals.Tableaux(['D',4], shape=[1])
+ sage: H = Hom(B, B)
+ sage: d = {1:1, 2:2, 3:4, 4:3}
+ sage: psi = H(B.module_generators, automorphism=d)
+ sage: b = B.highest_weight_vector()
+ sage: psi(b.f_string([1,2,3]))
+ [[-4]]
+ sage: psi(b.f_string([1,2,4]))
+ [[4]]
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ sage: psi(B.highest_weight_vector())
+ [[1, 1]]
+ """
+ mg, ef, indices = self.to_module_generator(x)
+ cur = self._on_gens(mg)
+ for op,i in reversed(zip(ef, indices)):
+ if cur is None:
+ return None
+
+ s = []
+ sf = self._scaling_factors[i]
+ for j in self._virtualization[i]:
+ s += [j]*sf
+ if op == 'e':
+ cur = cur.f_string(s)
+ elif op == 'f':
+ cur = cur.e_string(s)
+ return cur
+
+ def __nonzero__(self):
+ """
+ Return if ``self`` is a non-zero morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.elementary.Elementary(['A',2], 2)
+ sage: H = Hom(B, B)
+ sage: psi = H(B.module_generators)
+ sage: bool(psi)
+ True
+ sage: psi = H(lambda x: None)
+ sage: bool(psi)
+ False
+ """
+ return any(self._on_gens(mg) is not None for mg in self._gens)
+
+ # TODO: Does this belong in the element_class of the Crystals() category?
+ def to_module_generator(self, x):
+ """
+ Return a generator ``mg`` and a path of `e_i` and `f_i` operations
+ to ``mg``.
+
+ OUTPUT:
+
+ A tuple consisting of:
+
+ - a module generator,
+ - a list of ``'e'`` and ``'f'`` to denote which operation, and
+ - a list of matching indices.
+
+ EXAMPLES::
+
+ sage: B = crystals.elementary.Elementary(['A',2], 2)
+ sage: psi = B.crystal_morphism(B.module_generators)
+ sage: psi.to_module_generator(B(4))
+ (0, ['f', 'f', 'f', 'f'], [2, 2, 2, 2])
+ sage: psi.to_module_generator(B(-2))
+ (0, ['e', 'e'], [2, 2])
+ """
+ if x in self._path_mg_cache:
+ return self._path_mg_cache[x]
+
+ mg = set(self._path_mg_cache.keys())
+ visited = set([None, x])
+ index_set = self._cartan_type.index_set()
+ todo = [x]
+ ef = [[]]
+ indices = [[]]
+
+ while len(todo) > 0:
+ cur = todo.pop(0)
+ cur_ef = ef.pop(0)
+ cur_indices = indices.pop(0)
+ for i in index_set:
+ next = cur.e(i)
+ if next in mg:
+ gen,ef,indices = self._path_mg_cache[next]
+ ef = cur_ef + ['e'] + ef
+ indices = cur_indices + [i] + indices
+ self._path_mg_cache[x] = (gen, ef, indices)
+ return (gen, ef, indices)
+ if next not in visited:
+ todo.append(next)
+ ef.append(cur_ef + ['e'])
+ indices.append(cur_indices + [i])
+ visited.add(next)
+
+ # Now for f's
+ next = cur.f(i)
+ if next in mg:
+ gen,ef,indices = self._path_mg_cache[next]
+ ef = cur_ef + ['f'] + ef
+ indices = cur_indices + [i] + indices
+ self._path_mg_cache[x] = (gen, ef, indices)
+ return (gen, ef, indices)
+ if next not in visited:
+ todo.append(next)
+ ef.append(cur_ef + ['f'])
+ indices.append(cur_indices + [i])
+ visited.add(next)
+ raise ValueError("no module generator in the component of {}".format(x))
+
+ @cached_method
+ def im_gens(self):
+ """
+ Return the image of the generators of ``self`` as a tuple.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: F = crystals.Tableaux(['A',2], shape=[1])
+ sage: T = crystals.TensorProduct(F, F, F)
+ sage: H = Hom(T, B)
+ sage: b = B.highest_weight_vector()
+ sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors())
+ sage: psi.im_gens()
+ (None, [[1, 1], [2]], [[1, 1], [2]], None)
+ """
+ return tuple([self._on_gens(g) for g in self._gens])
+
+ def image(self):
+ """
+ Return the image of ``self`` in the codomain as a
+ :class:`~sage.combinat.crystals.subcrystal.Subcrystal`.
+
+ .. WARNING::
+
+ This assumes that ``self`` is a strict crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ sage: psi.image()
+ Virtual crystal of The crystal of tableaux of type ['D', 4] and shape(s) [[2]] of type ['B', 3]
+ """
+ #if not self.is_strict():
+ # raise NotImplementedError
+ from sage.combinat.crystals.subcrystal import Subcrystal
+ return Subcrystal(self.codomain(),
+ virtualization=self._virtualization,
+ scaling_factors=self._scaling_factors,
+ generators=self.im_gens(),
+ cartan_type=self._cartan_type,
+ index_set=self._cartan_type.index_set(),
+ category=self.domain().category())
+
+###############################################################################
+## Homset
+
+class CrystalHomset(Homset):
+ r"""
+ The set of crystal morphisms from one crystal to another.
+
+ An `U_q(\mathfrak{g})` `I`-crystal morphism `\Psi : B \to C` is a map
+ `\Psi : B \cup \{ 0 \} \to C \cup \{ 0 \}` such that:
+
+ - `\Psi(0) = 0`.
+ - If `b \in B` and `\Psi(b) \in C`, then
+ `\mathrm{wt}(\Psi(b)) = \mathrm{wt}(b)`,
+ `\varepsilon_i(\Psi(b)) = \varepsilon_i(b)`, and
+ `\varphi_i(\Psi(b)) = \varphi_i(b)` for all `i \in I`.
+ - If `b, b^{\prime} \in B`, `\Psi(b), \Psi(b^{\prime}) \in C` and
+ `f_i b = b^{\prime}`, then `f_i \Psi(b) = \Psi(b^{\prime})` and
+ `\Psi(b) = e_i \Psi(b^{\prime})` for all `i \in I`.
+
+ If the Cartan type is unambiguous, it is surpressed from the notation.
+
+ We can also generalize the definition of a crystal morphism by considering
+ a map of `\sigma` of the (now possibly different) Dynkin diagrams
+ corresponding to `B` and `C` along with scaling factors
+ `\gamma_i \in \ZZ` for `i \in I`. Let `\sigma_i` denote the orbit of
+ `i` under `\sigma`. We write objects for `B` as `X` with
+ corresponding objects of `C` as `\widehat{X}`.
+ Then a *virtual* crystal morphism `\Psi` is a map such that
+ the following holds:
+
+ - `\Psi(0) = 0`.
+ - If `b \in B` and `\Psi(b) \in C`, then for all `j \in \sigma_i`:
+
+ .. MATH::
+
+ \varepsilon_i(b) = \frac{1}{\gamma_j} \widehat{\varepsilon}_j(\Psi(b)),
+ \quad \varphi_i(b) = \frac{1}{\gamma_j} \widehat{\varphi}_j(\Psi(b)),
+ \quad \mathrm{wt}(\Psi(b)) = \sum_i c_i \sum_{j \in \sigma_i} \gamma_j
+ \widehat{\Lambda}_j,
+
+ where `\mathrm{wt}(b) = \sum_i c_i \Lambda_i`.
+
+ - If `b, b^{\prime} \in B`, `\Psi(b), \Psi(b^{\prime}) \in C` and
+ `f_i b = b^{\prime}`, then independent of the ordering of `\sigma_i`
+ we have:
+
+ .. MATH::
+
+ \Psi(b^{\prime}) = e_i \Psi(b) =
+ \prod_{j \in \sigma_i} \widehat{e}_j^{\gamma_i} \Psi(b), \quad
+ \Psi(b^{\prime}) = f_i \Psi(b) =
+ \prod_{j \in \sigma_i} \widehat{f}_j^{\gamma_i} \Psi(b).
+
+ If `\gamma_i = 1` for all `i \in I` and the Dynkin diagrams are
+ the same, then we call `\Psi` a *twisted* crystal morphism.
+
+ INPUT:
+
+ - ``X`` -- the domain
+ - ``Y`` -- the codomain
+ - ``category`` -- (optional) the category of the crystal morphisms
+
+ .. SEEALSO::
+
+ For the construction of an element of the homset, see
+ :class:`CrystalMorphismByGenerators` and
+ :meth:`~sage.categories.crystals.Crystals.ParentMethods.crystal_morphism`.
+
+ EXAMPLES:
+
+ We begin with the natural embedding of `B(2\Lambda_1)` into
+ `B(\Lambda_1) \otimes B(\Lambda_1)` in type `A_1`::
+
+ sage: B = crystals.Tableaux(['A',1], shape=[2])
+ sage: F = crystals.Tableaux(['A',1], shape=[1])
+ sage: T = crystals.TensorProduct(F, F)
+ sage: v = T.highest_weight_vectors()[0]; v
+ [[[1]], [[1]]]
+ sage: H = Hom(B, T)
+ sage: psi = H([v])
+ sage: b = B.highest_weight_vector(); b
+ [[1, 1]]
+ sage: psi(b)
+ [[[1]], [[1]]]
+ sage: b.f(1)
+ [[1, 2]]
+ sage: psi(b.f(1))
+ [[[1]], [[2]]]
+
+ We now look at the decomposition of `B(\Lambda_1) \otimes B(\Lambda_1)`
+ into `B(2\Lambda_1) \oplus B(0)`::
+
+ sage: B0 = crystals.Tableaux(['A',1], shape=[])
+ sage: D = crystals.DirectSum([B, B0])
+ sage: H = Hom(T, D)
+ sage: psi = H(D.module_generators)
+ sage: psi
+ ['A', 1] Crystal morphism:
+ From: Full tensor product of the crystals
+ [The crystal of tableaux of type ['A', 1] and shape(s) [[1]],
+ The crystal of tableaux of type ['A', 1] and shape(s) [[1]]]
+ To: Direct sum of the crystals Family
+ (The crystal of tableaux of type ['A', 1] and shape(s) [[2]],
+ The crystal of tableaux of type ['A', 1] and shape(s) [[]])
+ Defn: [[[1]], [[1]]] |--> [1, 1]
+ [[[2]], [[1]]] |--> []
+ sage: psi.is_isomorphism()
+ True
+
+ We can always construct the trivial morphism which sends
+ everything to `0`::
+
+ sage: Binf = crystals.infinity.Tableaux(['B', 2])
+ sage: B = crystals.Tableaux(['B',2], shape=[1])
+ sage: H = Hom(Binf, B)
+ sage: psi = H(lambda x: None)
+ sage: psi(Binf.highest_weight_vector())
+
+ For Kirillov-Reshetikhin crystals, we consider the map to the
+ corresponding classical crystal::
+
+ sage: K = crystals.KirillovReshetikhin(['D',4,1], 2,1)
+ sage: B = K.classical_decomposition()
+ sage: H = Hom(K, B)
+ sage: psi = H(lambda x: x.lift(), cartan_type=['D',4])
+ sage: L = [psi(mg) for mg in K.module_generators]; L
+ [[], [[1], [2]]]
+ sage: all(x.parent() == B for x in L)
+ True
+
+ Next we consider a type `D_4` crystal morphism where we twist by
+ `3 \leftrightarrow 4`::
+
+ sage: B = crystals.Tableaux(['D',4], shape=[1])
+ sage: H = Hom(B, B)
+ sage: d = {1:1, 2:2, 3:4, 4:3}
+ sage: psi = H(B.module_generators, automorphism=d)
+ sage: b = B.highest_weight_vector()
+ sage: b.f_string([1,2,3])
+ [[4]]
+ sage: b.f_string([1,2,4])
+ [[-4]]
+ sage: psi(b.f_string([1,2,3]))
+ [[-4]]
+ sage: psi(b.f_string([1,2,4]))
+ [[4]]
+
+ We construct the natural virtual embedding of a type `B_3` into a type
+ `D_4` crystal::
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ sage: psi
+ ['B', 3] -> ['D', 4] Virtual Crystal morphism:
+ From: The crystal of tableaux of type ['B', 3] and shape(s) [[1]]
+ To: The crystal of tableaux of type ['D', 4] and shape(s) [[2]]
+ Defn: [1] |--> [1, 1]
+ sage: for b in B: print "{} |--> {}".format(b, psi(b))
+ [1] |--> [1, 1]
+ [2] |--> [2, 2]
+ [3] |--> [3, 3]
+ [0] |--> [3, -3]
+ [-3] |--> [-3, -3]
+ [-2] |--> [-2, -2]
+ [-1] |--> [-1, -1]
+ """
+ def __init__(self, X, Y, category=None):
+ """
+ Initialize ``self``.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['A', 2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: Binf = crystals.infinity.Tableaux(['B',2])
+ sage: H = Hom(Binf, B)
+ """
+ if category is None:
+ category = Crystals()
+ # TODO: Should we make one of the types of morphisms into the self.Element?
+ Homset.__init__(self, X, Y, category)
+
+ def _repr_(self):
+ """
+ TESTS::
+
+ sage: B = crystals.Tableaux(['A', 2], shape=[2,1])
+ sage: Hom(B, B)
+ Set of Crystal Morphisms from The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ """
+ return "Set of Crystal Morphisms from {} to {}".format(self.domain(), self.codomain())
+
+ def _coerce_impl(self, x):
+ """
+ Check to see if we can coerce ``x`` into a morphism with the
+ correct parameters.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['B',3], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: H(H.an_element()) # indirect doctest
+ ['B', 3] Crystal endomorphism of The crystal of tableaux of type ['B', 3] and shape(s) [[2, 1]]
+ Defn: [2, 1, 1] |--> None
+ """
+ if not isinstance(x, CrystalMorphism):
+ raise TypeError
+
+ if x.parent() is self:
+ return x
+
+ # Case 1: the parent fits
+ if x.parent() == self:
+ return self.element_class(self, x._on_gens,
+ x._virtualization, x._scaling_factors,
+ x._cartan_type, x._gens)
+
+ # TODO: Should we try extraordinary measures (like twisting)?
+ raise ValueError
+
+ def __call__(self, on_gens, cartan_type=None, index_set=None, generators=None,
+ automorphism=None, virtualization=None, scaling_factors=None, check=True):
+ """
+ Construct a crystal morphism.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A', 2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: psi = H(B.module_generators)
+
+ sage: F = crystals.Tableaux(['A',3], shape=[1])
+ sage: T = crystals.TensorProduct(F, F, F)
+ sage: H = Hom(B, T)
+ sage: v = T.highest_weight_vectors()[2]
+ sage: psi = H([v], cartan_type=['A',2])
+ """
+ if isinstance(on_gens, CrystalMorphism):
+ return self._coerce_impl(on_gens)
+
+ if cartan_type is None:
+ cartan_type = self.domain().cartan_type()
+ else:
+ from sage.combinat.root_system.cartan_type import CartanType
+ cartan_type = CartanType(cartan_type)
+ if index_set is None:
+ index_set = cartan_type.index_set()
+ else:
+ cartan_type = cartan_type.subtype(index_set)
+
+ # Try as a natural folding
+ if cartan_type != self.codomain().cartan_type():
+ fct = self.domain().cartan_type().as_folding()
+ if fct.folding_of() == self.codomain().cartan_type():
+ if virtualization is None:
+ virtualization = fct.folding_orbit()
+ if scaling_factors is None:
+ scaling_factors = fct.scaling_factors()
+
+ if automorphism is not None:
+ if virtualization is not None:
+ raise ValueError("the automorphism and virtualization cannot both be specified")
+ if not isinstance(automorphism, dict):
+ try:
+ automorphism = dict(automorphism)
+ virtualization = {i: (automorphism[i],) for i in automorphism}
+ except (TypeError, ValueError):
+ virtualization = {i: (automorphism(i),) for i in index_set}
+ else:
+ virtualization = {i: (automorphism[i],) for i in automorphism}
+
+ return self.element_class(self, on_gens, cartan_type,
+ virtualization, scaling_factors,
+ generators, check)
+
+ def _an_element_(self):
+ """
+ Return an element of ``self``. Every homset has the crystal morphism
+ which sends all elements to ``None``.
+
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A', 2], shape=[2,1])
+ sage: C = crystals.infinity.Tableaux(['A', 2])
+ sage: H = Hom(B, C)
+ sage: H.an_element()
+ ['A', 2] Crystal morphism:
+ From: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ To: The infinity crystal of tableaux of type ['A', 2]
+ Defn: [2, 1, 1] |--> None
+ """
+ return self.element_class(self, lambda x: None)
+
+ Element = CrystalMorphismByGenerators
+
diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py
index e1c96a4..8bba29f 100644
--- a/src/sage/categories/finite_posets.py
+++ b/src/sage/categories/finite_posets.py
@@ -67,7 +67,8 @@ class FinitePosets(CategoryWithAxiom):
sage: P.is_lattice()
False
"""
- return self.is_meet_semilattice() and self.is_join_semilattice()
+ return (self.cardinality() == 0 or
+ (self.has_bottom() and self.is_join_semilattice()))
def is_selfdual(self):
r"""
diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py
index af374b2..25b64b2 100644
--- a/src/sage/categories/highest_weight_crystals.py
+++ b/src/sage/categories/highest_weight_crystals.py
@@ -10,7 +10,8 @@ Highest Weight Crystals
from sage.misc.cachefunc import cached_method
from sage.categories.category_singleton import Category_singleton
-from sage.categories.crystals import Crystals
+from sage.categories.crystals import Crystals, CrystalHomset, \
+ CrystalMorphismByGenerators
from sage.categories.tensor import TensorProductsCategory
class HighestWeightCrystals(Category_singleton):
@@ -150,10 +151,11 @@ class HighestWeightCrystals(Category_singleton):
else:
raise RuntimeError("The crystal does not have exactly one highest weight vector")
+ # TODO: Not every highest weight crystal is a lowest weight crystal
@cached_method
def lowest_weight_vectors(self):
r"""
- Returns the lowest weight vectors of ``self``
+ Return the lowest weight vectors of ``self``.
This default implementation selects among all elements of the crystal
those that are lowest weight, and cache the result.
@@ -164,16 +166,16 @@ class HighestWeightCrystals(Category_singleton):
sage: C = crystals.Letters(['A',5])
sage: C.lowest_weight_vectors()
- [6]
+ (6,)
::
sage: C = crystals.Letters(['A',2])
sage: T = crystals.TensorProduct(C,C,C,generators=[[C(2),C(1),C(1)],[C(1),C(2),C(1)]])
sage: T.lowest_weight_vectors()
- [[3, 2, 3], [3, 3, 2]]
+ ([3, 2, 3], [3, 3, 2])
"""
- return [g for g in self if g.is_lowest_weight()]
+ return tuple(g for g in self if g.is_lowest_weight())
def __iter__(self, index_set=None, max_depth = float("inf")):
"""
@@ -394,8 +396,48 @@ class HighestWeightCrystals(Category_singleton):
ret = P(ret, prec)
return ret
- class ElementMethods:
+ # TODO: This is not correct if a factor has multiple heads (i.e., we
+ # should have a category for uniqueness of highest/lowest weights)
+ connected_components_generators = highest_weight_vectors
+
+ def _Hom_(self, Y, category=None, **options):
+ r"""
+ Return the homset from ``self`` to ``Y`` in the
+ category ``category``.
+
+ INPUT:
+
+ - ``Y`` -- a crystal
+ - ``category`` -- a subcategory of :class:`HighestWeightCrysals`()
+ or ``None``
+
+ The sole purpose of this method is to construct the homset as a
+ :class:`~sage.categories.highest_weight_crystals.HighestWeightCrystalHomset`.
+ If ``category`` is specified and is not a subcategory of
+ :class:`HighestWeightCrystals`, a ``TypeError`` is raised instead
+
+ This method is not meant to be called directly. Please use
+ :func:`sage.categories.homset.Hom` instead.
+ EXAMPLES::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: H = B._Hom_(B)
+ sage: H
+ Set of Crystal Morphisms from The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ sage: type(H)
+ <class 'sage.categories.highest_weight_crystals.HighestWeightCrystalHomset_with_category'>
+ """
+ if category is None:
+ category = self.category()
+ elif not category.is_subcategory(HighestWeightCrystals()):
+ raise TypeError("{} is not a subcategory of HighestWeightCrystals()".format(category))
+ if Y not in Crystals():
+ raise TypeError("{} is not a crystal".format(Y))
+ return HighestWeightCrystalHomset(self, Y, category=category, **options)
+
+ class ElementMethods:
pass
class TensorProducts(TensorProductsCategory):
@@ -482,3 +524,147 @@ class HighestWeightCrystals(Category_singleton):
it.append( iter(self.crystals[-len(path)-1]) )
return tuple(ret)
+###############################################################################
+## Morphisms
+
+class HighestWeightCrystalMorphism(CrystalMorphismByGenerators):
+ r"""
+ A virtual crystal morphism whose domain is a highest weight crystal.
+
+ INPUT:
+
+ - ``parent`` -- a homset
+ - ``on_gens`` -- a function or list that determines the image of the
+ generators (if given a list, then this uses the order of the
+ generators of the domain) of the domain under ``self``
+ - ``cartan_type`` -- (optional) a Cartan type; the default is the
+ Cartan type of the domain
+ - ``virtualization`` -- (optional) a dictionary whose keys are
+ in the index set of the domain and whose values are lists of
+ entries in the index set of the codomain
+ - ``scaling_factors`` -- (optional) a dictionary whose keys are in
+ the index set of the domain and whose values are scaling factors
+ for the weight, `\varepsilon` and `\varphi`
+ - ``gens`` -- (optional) a list of generators to define the morphism;
+ the default is to use the highest weight vectors of the crystal
+ - ``check`` -- (default: ``True``) check if the crystal morphism is valid
+ """
+ def __init__(self, parent, on_gens, cartan_type=None,
+ virtualization=None, scaling_factors=None,
+ gens=None, check=True):
+ """
+ Construct a crystal morphism.
+
+ TESTS::
+
+ sage: B = crystals.infinity.Tableaux(['B',2])
+ sage: C = crystals.infinity.NakajimaMonomials(['B',2])
+ sage: psi = B.crystal_morphism(C.module_generators)
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ """
+ if cartan_type is None:
+ cartan_type = parent.domain().cartan_type()
+ if isinstance(on_gens, dict):
+ gens = on_gens.keys()
+ I = cartan_type.index_set()
+ if gens is None:
+ if cartan_type == parent.domain().cartan_type():
+ gens = parent.domain().highest_weight_vectors()
+ else:
+ gens = tuple(x for x in parent.domain() if x.is_highest_weight(I))
+ self._hw_gens = True
+ elif check:
+ self._hw_gens = all(x.is_highest_weight(I) for x in gens)
+ else:
+ self._hw_gens = False
+ CrystalMorphismByGenerators.__init__(self, parent, on_gens, cartan_type,
+ virtualization, scaling_factors,
+ gens, check)
+
+ def _call_(self, x):
+ """
+ Return the image of ``x`` under ``self``.
+
+ TESTS::
+
+ sage: B = crystals.infinity.Tableaux(['B',2])
+ sage: C = crystals.infinity.NakajimaMonomials(['B',2])
+ sage: psi = B.crystal_morphism(C.module_generators)
+ sage: b = B.highest_weight_vector()
+ sage: psi(b)
+ 1
+ sage: c = psi(b.f_string([1,1,1,2,2,1,2,2])); c
+ Y(1,0)^-4 Y(2,0)^4 Y(2,1)^-4
+ sage: c == C.highest_weight_vector().f_string([1,1,1,2,2,1,2,2])
+ True
+
+ sage: B = crystals.Tableaux(['B',3], shape=[1])
+ sage: C = crystals.Tableaux(['D',4], shape=[2])
+ sage: H = Hom(B, C)
+ sage: psi = H(C.module_generators)
+ sage: psi(B.module_generators[0])
+ [[1, 1]]
+
+ We check with the morphism defined on the lowest weight vector::
+
+ sage: B = crystals.Tableaux(['A',2], shape=[1])
+ sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights()
+ sage: T = crystals.elementary.T(['A',2], La[2])
+ sage: Bp = T.tensor(B)
+ sage: C = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: H = Hom(Bp, C)
+ sage: x = C.module_generators[0].f_string([1,2])
+ sage: psi = H({Bp.lowest_weight_vectors()[0]: x})
+ sage: psi
+ ['A', 2] Crystal morphism:
+ From: Full tensor product of the crystals [The T crystal of type ['A', 2] and weight (1, 1, 0), The crystal of tableaux of type ['A', 2] and shape(s) [[1]]]
+ To: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]]
+ Defn: [(1, 1, 0), [[3]]] |--> [2, 1, 3]
+ sage: psi(Bp.highest_weight_vector())
+ [[1, 1], [2]]
+ """
+ if not self._hw_gens:
+ return CrystalMorphismByGenerators._call_(self, x)
+ mg, path = x.to_highest_weight(self._cartan_type.index_set())
+ cur = self._on_gens(mg)
+ for i in reversed(path):
+ if cur is None:
+ return None
+ s = []
+ sf = self._scaling_factors[i]
+ for j in self._virtualization[i]:
+ s += [j]*sf
+ cur = cur.f_string(s)
+ return cur
+
+class HighestWeightCrystalHomset(CrystalHomset):
+ """
+ The set of crystal morphisms from a highest weight crystal to
+ another crystal.
+
+ .. SEEALSO::
+
+ See :class:`sage.categories.crystals.CrystalHomset` for more
+ information.
+ """
+ def __init__(self, X, Y, category=None):
+ """
+ Initialize ``self``.
+
+ TESTS::
+
+ sage: B = crystals.Tableaux(['A', 2], shape=[2,1])
+ sage: H = Hom(B, B)
+ sage: B = crystals.infinity.Tableaux(['B',2])
+ sage: H = Hom(B, B)
+ """
+ if category is None:
+ category = HighestWeightCrystals()
+ CrystalHomset.__init__(self, X, Y, category)
+
+ Element = HighestWeightCrystalMorphism
+
diff --git a/src/sage/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py
index cd44b42..b1aecb4 100644
--- a/src/sage/categories/regular_crystals.py
+++ b/src/sage/categories/regular_crystals.py
@@ -21,6 +21,7 @@ from sage.misc.cachefunc import cached_method
from sage.categories.category_singleton import Category_singleton
from sage.categories.crystals import Crystals
from sage.categories.tensor import TensorProductsCategory
+from sage.combinat.subset import Subsets
from sage.graphs.dot2tex_utils import have_dot2tex
class RegularCrystals(Category_singleton):
@@ -122,6 +123,38 @@ class RegularCrystals(Category_singleton):
"""
return None
+ class MorphismMethods:
+ def is_isomorphism(self):
+ """
+ Check if ``self`` is a crystal isomorphism, which is true
+ if and only if this is a strict embedding with the same number
+ of connected components.
+
+ EXAMPLES::
+
+ sage: La = RootSystem(['A',2,1]).weight_space(extended=True).fundamental_weights()
+ sage: B = crystals.LSPaths(La[0])
+ sage: La = RootSystem(['A',2,1]).weight_lattice(extended=True).fundamental_weights()
+ sage: C = crystals.GeneralizedYoungWalls(2, La[0])
+ sage: H = Hom(B, C)
+ sage: from sage.categories.highest_weight_crystals import HighestWeightCrystalMorphism
+ sage: class Psi(HighestWeightCrystalMorphism):
+ ....: def is_strict(self):
+ ....: return True
+ sage: psi = Psi(H, C.module_generators)
+ sage: psi
+ ['A', 2, 1] Crystal morphism:
+ From: The crystal of LS paths of type ['A', 2, 1] and weight Lambda[0]
+ To: Highest weight crystal of generalized Young walls of Cartan type ['A', 2, 1]
+ and highest weight Lambda[0]
+ Defn: (Lambda[0],) |--> []
+ sage: psi.is_isomorphism()
+ True
+ """
+ return (self.is_strict()
+ and self.domain().number_of_connected_components() ==
+ self.codomain().number_of_connected_components())
+
class ParentMethods:
# TODO: this could be a method in Crystals.Algebras.ElementMethods, so that
@@ -176,6 +209,64 @@ class RegularCrystals(Category_singleton):
for c, coeff in element)
return element
+ def demazure_subcrystal(self, element, reduced_word, only_support=True):
+ r"""
+ Return the subcrystal corresponding to the application of
+ Demazure operators `D_i` for `i` from ``reduced_word`` on
+ ``element``.
+
+ INPUT:
+
+ - ``element`` -- an element of a free module indexed by the
+ underlying crystal
+ - ``reduced_word`` -- a reduced word of the Weyl group of the
+ same type as the underlying crystal
+ - ``only_support`` -- (default: ``True``) only include arrows
+ corresponding the the support of ``reduced_word``
+
+ OUTPUT:
+
+ - the Demazure subcrystal
+
+ EXAMPLES::
+
+ sage: T = crystals.Tableaux(['A',2], shape=[2,1])
+ sage: t = T.highest_weight_vector()
+ sage: S = T.demazure_subcrystal(t, [1,2])
+ sage: list(S)
+ [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]],
+ [[1, 2], [3]], [[2, 2], [3]]]
+ sage: S = T.demazure_subcrystal(t, [2,1])
+ sage: list(S)
+ [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]],
+ [[1, 3], [2]], [[1, 3], [3]]]
+
+ We construct an example where we don't only want the arrows
+ indicated by the support of the reduced word::
+
+ sage: K = crystals.KirillovReshetikhin(['A',1,1], 1, 2)
+ sage: mg = K.module_generator()
+ sage: S = K.demazure_subcrystal(mg, [1])
+ sage: S.digraph().edges()
+ [([[1, 1]], [[1, 2]], 1), ([[1, 2]], [[2, 2]], 1)]
+ sage: S = K.demazure_subcrystal(mg, [1], only_support=False)
+ sage: S.digraph().edges()
+ [([[1, 1]], [[1, 2]], 1),
+ ([[1, 2]], [[1, 1]], 0),
+ ([[1, 2]], [[2, 2]], 1),
+ ([[2, 2]], [[1, 2]], 0)]
+ """
+ from sage.combinat.free_module import CombinatorialFreeModule
+ from sage.rings.all import QQ
+ C = CombinatorialFreeModule(QQ, self)
+ D = self.demazure_operator(C(element), reduced_word)
+ if only_support:
+ index_set = tuple(frozenset(reduced_word))
+ else:
+