diff --git a/src/sage/combinat/knutson_tao_puzzles.py b/src/sage/combinat/knutson_tao_puzzles.py index 7e226bffee3..20524d89472 100644 --- a/src/sage/combinat/knutson_tao_puzzles.py +++ b/src/sage/combinat/knutson_tao_puzzles.py @@ -6,11 +6,13 @@ north-east and north-west sides of the puzzle boundary; the output is the list of the fillings of the puzzle with the specified pieces. -Acknowledgements ----------------- -This code was written during Sage Days 45 at ICERM with Franco Saliola, Anne Schilling, and Avinash Dalal in discussions with Allen Knutson. -The code was tested afterwards by Liz Beazley and Ed Richmond. +AUTHORS: + +- Franco Saliola, Allen Knutson, Avinash Dalal, Anne Schilling (2013): initial version at Sage Days 45, ICERM +- Elizabeth Beazley, Ed Richmond (2013): testing +- Álvaro Gutiérrez (2025-02-24): added ``to_littlewood_richardson_tableau`` +- Julian Rüth (2025-02-24): clean up code .. TODO:: @@ -21,10 +23,6 @@ - we should also have a 3-step puzzle pieces constructor, taken from p22 of :arxiv:`math/0610538` - - - implement the bijection from puzzles to tableaux; see for example - R. Vakil, A geometric Littlewood-Richardson rule, :arxiv:`math/0302294` - or K. Purbhoo, Puzzles, Tableaux and Mosaics, :arxiv:`0705.1184`. """ # **************************************************************************** # Copyright (C) 2013 Franco Saliola , @@ -32,11 +30,17 @@ # 2013 Avinash Dalal, # 2013 Anne Schilling, # 2013 Elizabeth Beazley, -# 2013 Ed Richmond +# 2013 Ed Richmond, +# 2025 Álvaro Gutiérrez +# 2025 Julian Rüth # -# Distributed under the terms of the GNU General Public License (GPL) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** + from __future__ import annotations from sage.misc.lazy_import import lazy_import @@ -634,14 +638,23 @@ class PuzzlePieces: sage: pieces.add_piece(NablaPiece('1','1','1'), rotations=60) sage: pieces.add_piece(NablaPiece('1','0','10'), rotations=60) sage: pieces - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) + The user can obtain the list of valid rhombi pieces as follows:: - sage: sorted([p for p in pieces.rhombus_pieces()], key=str) - [0/\0 0\/0, 0/\0 1\/10, 0/\10 10\/0, 0/\10 1\/1, 1/\0 0\/1, - 1/\1 10\/0, 1/\1 1\/1, 10/\1 0\/0, 10/\1 1\/10] + sage: pieces.rhombus_pieces() + (0/\0 0\/0, + 10/\1 0\/0, + 1/\1 1\/1, + 0/\10 1\/1, + 1/\1 10\/0, + 0/\10 10\/0, + 1/\0 0\/1, + 0/\0 1\/10, + 10/\1 1\/10) + """ def __init__(self, forbidden_border_labels=None): @@ -656,16 +669,16 @@ def __init__(self, forbidden_border_labels=None): sage: forbidden_border_labels = ['10'] sage: pieces = PuzzlePieces(forbidden_border_labels) sage: pieces - Nablas : [] - Deltas : [] + Nablas : () + Deltas : () sage: PuzzlePieces('10') Traceback (most recent call last): ... TypeError: Input must be a list """ - self._nabla_pieces = set() - self._delta_pieces = set() + self._nabla_pieces = {} + self._delta_pieces = {} if forbidden_border_labels is None: forbidden_border_labels = [] if not isinstance(forbidden_border_labels, list): @@ -721,33 +734,33 @@ def add_piece(self, piece, rotations=120): sage: delta = DeltaPiece('a','b','c') sage: pieces = PuzzlePieces() sage: pieces - Nablas : [] - Deltas : [] + Nablas : () + Deltas : () sage: pieces.add_piece(delta) sage: pieces - Nablas : [] - Deltas : [a/c\b, b/a\c, c/b\a] + Nablas : () + Deltas : (b/a\c, a/c\b, c/b\a) sage: pieces = PuzzlePieces() sage: pieces.add_piece(delta,rotations=0) sage: pieces - Nablas : [] - Deltas : [b/a\c] + Nablas : () + Deltas : (b/a\c,) sage: pieces = PuzzlePieces() sage: pieces.add_piece(delta,rotations=60) sage: pieces - Nablas : [a\b/c, b\c/a, c\a/b] - Deltas : [a/c\b, b/a\c, c/b\a] + Nablas : (c\a/b, b\c/a, a\b/c) + Deltas : (b/a\c, a/c\b, c/b\a) """ if isinstance(piece, NablaPiece): pieces_list = self._nabla_pieces else: pieces_list = self._delta_pieces - pieces_list.add(piece) + pieces_list[piece] = True if rotations == 120: - pieces_list.add(piece.clockwise_rotation()) - pieces_list.add(piece.clockwise_rotation().clockwise_rotation()) + pieces_list[piece.clockwise_rotation()] = True + pieces_list[piece.clockwise_rotation().clockwise_rotation()] = True elif rotations == 180: self.add_piece(piece.half_turn_rotation(), rotations=0) elif rotations == 60: @@ -789,8 +802,8 @@ def add_T_piece(self, label1, label2): sage: pieces = PuzzlePieces() sage: pieces.add_T_piece('1','3') sage: pieces - Nablas : [3\T1|3/1] - Deltas : [1/T1|3\3] + Nablas : (3\T1|3/1,) + Deltas : (1/T1|3\3,) sage: pieces._forbidden_border_labels ['T1|3'] """ @@ -806,11 +819,12 @@ def __repr__(self) -> str: sage: delta = DeltaPiece('a','b','c') sage: pieces.add_piece(delta,rotations=60) sage: pieces - Nablas : [a\b/c, b\c/a, c\a/b] - Deltas : [a/c\b, b/a\c, c/b\a] + Nablas : (c\a/b, b\c/a, a\b/c) + Deltas : (b/a\c, a/c\b, c/b\a) + """ - s = "Nablas : %s\n" % sorted(self._nabla_pieces, key=str) - s += "Deltas : %s" % sorted(self._delta_pieces, key=str) + s = f"Nablas : {self.nabla_pieces()}\n" + s += f"Deltas : {self.delta_pieces()}" return s def delta_pieces(self): @@ -823,10 +837,11 @@ def delta_pieces(self): sage: pieces = PuzzlePieces() sage: delta = DeltaPiece('a','b','c') sage: pieces.add_piece(delta,rotations=60) - sage: sorted([p for p in pieces.delta_pieces()], key=str) - [a/c\b, b/a\c, c/b\a] + sage: pieces.delta_pieces() + (b/a\c, a/c\b, c/b\a) + """ - return self._delta_pieces + return tuple(self._delta_pieces) def nabla_pieces(self): r""" @@ -838,12 +853,13 @@ def nabla_pieces(self): sage: pieces = PuzzlePieces() sage: delta = DeltaPiece('a','b','c') sage: pieces.add_piece(delta,rotations=60) - sage: sorted([p for p in pieces.nabla_pieces()], key=str) - [a\b/c, b\c/a, c\a/b] + sage: pieces.nabla_pieces() + (c\a/b, b\c/a, a\b/c) + """ - return self._nabla_pieces + return tuple(self._nabla_pieces) - def rhombus_pieces(self) -> set: + def rhombus_pieces(self) -> tuple: r""" Return a set of all allowable rhombus pieces. @@ -856,15 +872,16 @@ def rhombus_pieces(self) -> set: sage: pieces = PuzzlePieces() sage: delta = DeltaPiece('a','b','c') sage: pieces.add_piece(delta,rotations=60) - sage: sorted([p for p in pieces.rhombus_pieces()], key=str) - [a/\b b\/a, b/\c c\/b, c/\a a\/c] + sage: pieces.rhombus_pieces() + (b/\c c\/b, a/\b b\/a, c/\a a\/c) + """ - rhombi = set() + rhombi = {} for nabla in self._nabla_pieces: for delta in self._delta_pieces: if delta['south'] == nabla['north']: - rhombi.add(RhombusPiece(delta, nabla)) - return rhombi + rhombi[RhombusPiece(delta, nabla)] = True + return tuple(rhombi) def boundary_deltas(self) -> tuple: r""" @@ -876,8 +893,8 @@ def boundary_deltas(self) -> tuple: sage: pieces = PuzzlePieces(['a']) sage: delta = DeltaPiece('a','b','c') sage: pieces.add_piece(delta,rotations=60) - sage: sorted([p for p in pieces.boundary_deltas()], key=str) - [a/c\b, c/b\a] + sage: pieces.boundary_deltas() + (a/c\b, c/b\a) """ return tuple(delta for delta in self.delta_pieces() if delta['south'] not in self._forbidden_border_labels) @@ -897,8 +914,8 @@ def H_grassmannian_pieces(): sage: from sage.combinat.knutson_tao_puzzles import H_grassmannian_pieces sage: H_grassmannian_pieces() - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) """ forbidden_border_labels = ['10'] pieces = PuzzlePieces(forbidden_border_labels) @@ -921,8 +938,8 @@ def HT_grassmannian_pieces(): sage: from sage.combinat.knutson_tao_puzzles import HT_grassmannian_pieces sage: HT_grassmannian_pieces() - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1, 1\T0|1/0] - Deltas : [0/0\0, 0/1\10, 0/T0|1\1, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10, 1\T0|1/0) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1, 0/T0|1\1) """ pieces = H_grassmannian_pieces() pieces.add_T_piece('0', '1') @@ -941,8 +958,8 @@ def K_grassmannian_pieces(): sage: from sage.combinat.knutson_tao_puzzles import K_grassmannian_pieces sage: K_grassmannian_pieces() - Nablas : [0\0/0, 0\10/1, 0\K/1, 10\1/0, 1\0/10, 1\0/K, 1\1/1, K\1/0] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1, K/K\K] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10, 1\0/K, K\1/0, 0\K/1) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1, K/K\K) """ pieces = H_grassmannian_pieces() pieces.add_forbidden_label('K') @@ -965,10 +982,8 @@ def H_two_step_pieces(): sage: from sage.combinat.knutson_tao_puzzles import H_two_step_pieces sage: H_two_step_pieces() - Nablas : [(21)0\21/0, 0\(21)0/21, 0\0/0, 0\10/1, 0\20/2, 10\1/0, 10\2(10)/2, 1\0/10, 1\1/1, 1\21/2, - 2(10)\2/10, 20\2/0, 21\0/(21)0, 21\2/1, 2\0/20, 2\1/21, 2\10/2(10), 2\2/2] - Deltas : [(21)0/0\21, 0/0\0, 0/1\10, 0/21\(21)0, 0/2\20, 1/10\0, 1/1\1, 1/2\21, 10/0\1, 10/2\2(10), - 2(10)/10\2, 2/2(10)\10, 2/20\0, 2/21\1, 2/2\2, 20/0\2, 21/(21)0\0, 21/1\2] + Nablas : (0\0/0, 1\1/1, 2\2/2, 0\10/1, 1\0/10, 10\1/0, 0\20/2, 2\0/20, 20\2/0, 1\21/2, 2\1/21, 21\2/1, 0\(21)0/21, 21\0/(21)0, (21)0\21/0, 10\2(10)/2, 2\10/2(10), 2(10)\2/10) + Deltas : (0/0\0, 1/1\1, 2/2\2, 1/10\0, 10/0\1, 0/1\10, 2/20\0, 20/0\2, 0/2\20, 2/21\1, 21/1\2, 1/2\21, 21/(21)0\0, (21)0/0\21, 0/21\(21)0, 2/2(10)\10, 2(10)/10\2, 10/2\2(10)) """ forbidden_border_labels = ['10', '20', '21', '(21)0', '2(10)'] pieces = PuzzlePieces(forbidden_border_labels) @@ -996,12 +1011,8 @@ def HT_two_step_pieces(): sage: from sage.combinat.knutson_tao_puzzles import HT_two_step_pieces sage: HT_two_step_pieces() - Nablas : [(21)0\21/0, 0\(21)0/21, 0\0/0, 0\10/1, 0\20/2, 10\1/0, 10\2(10)/2, - 1\0/10, 1\1/1, 1\21/2, 1\T0|1/0, 2(10)\2/10, 20\2/0, 21\0/(21)0, 21\2/1, 21\T0|21/0, - 21\T10|21/10, 2\0/20, 2\1/21, 2\10/2(10), 2\2/2, 2\T0|2/0, 2\T10|2/10, 2\T1|2/1] - Deltas : [(21)0/0\21, 0/0\0, 0/1\10, 0/21\(21)0, 0/2\20, 0/T0|1\1, 0/T0|21\21, 0/T0|2\2, - 1/10\0, 1/1\1, 1/2\21, 1/T1|2\2, 10/0\1, 10/2\2(10), 10/T10|21\21, 10/T10|2\2, 2(10)/10\2, - 2/2(10)\10, 2/20\0, 2/21\1, 2/2\2, 20/0\2, 21/(21)0\0, 21/1\2] + Nablas : (0\0/0, 1\1/1, 2\2/2, 0\10/1, 1\0/10, 10\1/0, 0\20/2, 2\0/20, 20\2/0, 1\21/2, 2\1/21, 21\2/1, 0\(21)0/21, 21\0/(21)0, (21)0\21/0, 10\2(10)/2, 2\10/2(10), 2(10)\2/10, 1\T0|1/0, 2\T0|2/0, 2\T1|2/1, 2\T10|2/10, 21\T0|21/0, 21\T10|21/10) + Deltas : (0/0\0, 1/1\1, 2/2\2, 1/10\0, 10/0\1, 0/1\10, 2/20\0, 20/0\2, 0/2\20, 2/21\1, 21/1\2, 1/2\21, 21/(21)0\0, (21)0/0\21, 0/21\(21)0, 2/2(10)\10, 2(10)/10\2, 10/2\2(10), 0/T0|1\1, 0/T0|2\2, 1/T1|2\2, 10/T10|2\2, 0/T0|21\21, 10/T10|21\21) """ pieces = H_two_step_pieces() for label1, label2 in (('0', '1'), ('0', '2'), ('1', '2'), @@ -1042,8 +1053,8 @@ def BK_pieces(max_letter): sage: from sage.combinat.knutson_tao_puzzles import BK_pieces sage: BK_pieces(3) - Nablas : [1\1/1, 1\2(1)/2, 1\3(1)/3, 2(1)\2/1, 2\1/2(1), 2\2/2, 2\3(2)/3, 3(1)\3/1, 3(2)\3/2, 3\1/3(1), 3\2/3(2), 3\3/3] - Deltas : [1/1\1, 1/2\2(1), 1/3\3(1), 2(1)/1\2, 2/2(1)\1, 2/2\2, 2/3\3(2), 3(1)/1\3, 3(2)/2\3, 3/3(1)\1, 3/3(2)\2, 3/3\3] + Nablas : (1\1/1, 2\2/2, 1\2(1)/2, 2\1/2(1), 2(1)\2/1, 3\3/3, 1\3(1)/3, 3\1/3(1), 3(1)\3/1, 2\3(2)/3, 3\2/3(2), 3(2)\3/2) + Deltas : (1/1\1, 2/2\2, 2/2(1)\1, 2(1)/1\2, 1/2\2(1), 3/3\3, 3/3(1)\1, 3(1)/1\3, 1/3\3(1), 3/3(2)\2, 3(2)/2\3, 2/3\3(2)) """ forbidden_border_labels = ['%s(%s)' % (i, j) for i in range(1, max_letter + 1) @@ -1260,7 +1271,6 @@ def copy(self): EXAMPLES:: - sage: from sage.combinat.knutson_tao_puzzles import DeltaPiece, PuzzleFilling sage: piece = DeltaPiece('0','1','0') sage: P = PuzzleFilling('0101','0101'); P @@ -1286,7 +1296,7 @@ def contribution(self): sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver sage: ps = KnutsonTaoPuzzleSolver("HT") sage: puzzles = ps('0101','1001') - sage: sorted([p.contribution() for p in puzzles], key=str) + sage: [p.contribution() for p in puzzles] [1, y1 - y3] """ R = PolynomialRing(Integers(), 'y', self._n + 1) @@ -1313,6 +1323,19 @@ def __repr__(self): from pprint import pformat return pformat(self._squares) + def __eq__(self, other): + r""" + TESTS:: + + sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver + sage: ps = KnutsonTaoPuzzleSolver("H") + sage: ps('0101','1001') == ps('0101','1001') + True + sage: ps('0110','1001') == ps('0101','1001') + False + """ + return self._squares == other._squares + def __iter__(self): r""" Iterator. @@ -1464,8 +1487,138 @@ def tikzlabels(color, k, d, i, label1, label2, label3): return s + def to_littlewood_richardson_tableau(self): + r""" + Create a skew Littlewood--Richardson tableau from a puzzle. + + We follow the bijection given in [Purbhoo07]_. A similar but different + bijection is given in [Vakil03]_. + + .. WARNING:: -class KnutsonTaoPuzzleSolver(UniqueRepresentation): + This method only works for the classical cohomology puzzle pieces. + + REFERENCES: + + .. [Purbhoo07] K. Purbhoo, Puzzles, Tableaux and Mosaics, :arxiv:`0705.1184` + + .. [Vakil03] R. Vakil, A geometric Littlewood-Richardson rule, :arxiv:`0302294` + + OUTPUT: + + - a Littlewood--Richardson skew tableau + + EXAMPLES:: + + sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver + sage: ps = KnutsonTaoPuzzleSolver("H") + sage: solns = ps('010101','010101') + sage: [puzzle.to_littlewood_richardson_tableau() for puzzle in solns] + [[[None, None, None], [1, 1], [2]], + [[None, None, 1], [None, 1], [2]], + [[None, None, 1], [None, 2], [1]], + [[None, 1, 1], [None, 2], [None]]] + sage: # Example similar to Figure 9 in Purbhoo07 + ....: ps = KnutsonTaoPuzzleSolver('H') + ....: solns = ps('00000010001000010100', '00000000100010010100') + ....: puzzle = solns[168] + ....: tab = puzzle.to_littlewood_richardson_tableau(); tab.pp() + . . . . . . . . . . 1 1 1 1 + . . . . . . . 1 1 1 2 2 2 + . . . . . 1 1 2 2 3 3 + . . 1 2 2 3 4 4 + """ + from sage.combinat.partition import abacus_to_partition + from sage.combinat.skew_tableau import SkewTableau + + k = sum(int(i) for i in self._ne_labels) + lam = abacus_to_partition(self.south_labels()) + [0]*k + tab = [] + for i in range(self._n): + row = [] + if self._ne_labels[i] == '1': + k -= 1 + row += [None] * lam[k] + row += self._ne_to_south_path(i+1) + tab.insert(0, row) + while [] in tab: + tab.remove([]) + return SkewTableau(tab) + + def _ne_to_south_path(self, coord): + r""" + Return the content row of the Littlewood--Richardson skew tableau corresponding to ``coord``. + + This method traces out a path from ``coord`` to its "mirror" coordinate. + If ``coord`` specifies the `i`-th 1 from the top on the north-east border + of the puzzle, then its mirror is the `i`-th 1 from the west on the south + border. The algorithm records the content numbers of the traced horizontal + rhombi. See [Purbhoo07]_. + + .. WARNING:: + + This method only works for the classical cohomology puzzle pieces. + + REFERENCES: + + .. [Purbhoo07] K. Purbhoo, Puzzles, Tableaux and Mosaics, :arxiv:`0705.1184` + + INPUT: + + - ``coord`` -- north-east boundary position counting from the top whose label is 1 + + OUTPUT: + + - a list of numbers giving the content of one row in the Littlewood--Richardson tableau + + TESTS:: + + sage: ps = KnutsonTaoPuzzleSolver("H") + sage: solns = ps('0101011','0101101') + sage: puzzle = solns[4] + sage: puzzle[(1,1)] + 0/0\0 + sage: puzzle._ne_to_south_path(4) + [2] + sage: puzzle._ne_to_south_path(3) + Traceback (most recent call last): + ... + AssertionError: the coordinate needs to be a coordinate of a 1 on the north-east boundary + """ + assert self._ne_labels[coord-1] == '1', "the coordinate needs to be a coordinate of a 1 on the north-east boundary" + + i = coord + j = self._n + moving = "west" + LR_list = [] + while j != 0: + if moving == "west": + if j-i == 0: + current_piece = self[(i,j)] + else: + current_piece = self[(i,j)].north_piece() + current_labels = current_piece.border() + if current_labels == ('1', '1', '1'): + moving = "south" + LR_list = [i+1 for i in LR_list] + elif current_labels == ('0', '10', '1'): + i = i-1 + j = j-1 + LR_list.insert(0, 0) + elif moving == "south": + if j-i < 1: + break + current_piece = self[(i,j)].south_piece() + current_labels = current_piece.border() + if current_labels == ('1', '1', '1'): + moving = "west" + j = j-1 + assert j > 0, "something went wrong and the path escaped the puzzle" + + return LR_list + + +class KnutsonTaoPuzzleSolver(): r""" Return puzzle solver function used to create all puzzles with given boundary conditions. @@ -1499,8 +1652,8 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: from sage.combinat.knutson_tao_puzzles import H_grassmannian_pieces sage: H_grassmannian_pieces() - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) In the string representation, the nabla pieces are depicted as ``c\a/b``, where `a` is the label of the north edge, `b` is the label @@ -1511,15 +1664,15 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: KnutsonTaoPuzzleSolver(H_grassmannian_pieces()) Knutson-Tao puzzle solver with pieces: - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) The following shorthand to create the above puzzle solver is also supported:: sage: KnutsonTaoPuzzleSolver('H') Knutson-Tao puzzle solver with pieces: - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) The solver will compute all fillings of the puzzle with the given puzzle pieces. The user specifies the labels of north-east and @@ -1539,18 +1692,8 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: solns = ps('0101', '0101') sage: len(solns) 2 - sage: solns.sort(key=str) sage: solns - [{(1, 1): 0/0\0, - (1, 2): 1/\0 0\/1, - (1, 3): 0/\0 0\/0, - (1, 4): 1/\0 0\/1, - (2, 2): 1/1\1, - (2, 3): 0/\10 1\/1, - (2, 4): 1/\1 10\/0, - (3, 3): 1/1\1, - (3, 4): 0/\0 1\/10, - (4, 4): 10/0\1}, {(1, 1): 0/1\10, + [{(1, 1): 0/1\10, (1, 2): 1/\1 10\/0, (1, 3): 0/\0 1\/10, (1, 4): 1/\0 0\/1, @@ -1559,7 +1702,17 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (2, 4): 1/\1 1\/1, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, - (4, 4): 1/1\1}] + (4, 4): 1/1\1}, + {(1, 1): 0/0\0, + (1, 2): 1/\0 0\/1, + (1, 3): 0/\0 0\/0, + (1, 4): 1/\0 0\/1, + (2, 2): 1/1\1, + (2, 3): 0/\10 1\/1, + (2, 4): 1/\1 10\/0, + (3, 3): 1/1\1, + (3, 4): 0/\0 1\/10, + (4, 4): 10/0\1}] The pieces in a puzzle filling are indexed by pairs of nonnegative integers `(i, j)` with `1 \leq i \leq j \leq n`, where `n` is the @@ -1568,7 +1721,7 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: f = solns[0] sage: [f[i, i] for i in range(1,5)] - [0/0\0, 1/1\1, 1/1\1, 10/0\1] + [0/1\10, 0/0\0, 0/0\0, 1/1\1] The pieces indexed by `(i, j)` for `j > i` are a pair consisting of a delta piece and nabla piece glued together along the south edge and @@ -1576,7 +1729,7 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: f = solns[0] sage: f[1, 2] - 1/\0 0\/1 + 1/\1 10\/0 There are various methods and options to display puzzle solutions. A single puzzle can be displayed using the plot method of the puzzle:: @@ -1601,24 +1754,13 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): sage: solns = ps('0101', '0101') sage: view(solns[0], viewer='pdf') # not tested - Below are examples of using each of the currently supported puzzles. Cohomology of the Grassmannian:: sage: ps = KnutsonTaoPuzzleSolver("H") - sage: solns = ps('0101', '0101') - sage: sorted(solns, key=str) - [{(1, 1): 0/0\0, - (1, 2): 1/\0 0\/1, - (1, 3): 0/\0 0\/0, - (1, 4): 1/\0 0\/1, - (2, 2): 1/1\1, - (2, 3): 0/\10 1\/1, - (2, 4): 1/\1 10\/0, - (3, 3): 1/1\1, - (3, 4): 0/\0 1\/10, - (4, 4): 10/0\1}, {(1, 1): 0/1\10, + sage: ps('0101', '0101') + [{(1, 1): 0/1\10, (1, 2): 1/\1 10\/0, (1, 3): 0/\0 1\/10, (1, 4): 1/\0 0\/1, @@ -1627,23 +1769,33 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (2, 4): 1/\1 1\/1, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, - (4, 4): 1/1\1}] + (4, 4): 1/1\1}, + {(1, 1): 0/0\0, + (1, 2): 1/\0 0\/1, + (1, 3): 0/\0 0\/0, + (1, 4): 1/\0 0\/1, + (2, 2): 1/1\1, + (2, 3): 0/\10 1\/1, + (2, 4): 1/\1 10\/0, + (3, 3): 1/1\1, + (3, 4): 0/\0 1\/10, + (4, 4): 10/0\1}] Equivariant puzzles:: sage: ps = KnutsonTaoPuzzleSolver("HT") - sage: solns = ps('0101', '0101') - sage: sorted(solns, key=str) - [{(1, 1): 0/0\0, - (1, 2): 1/\0 0\/1, - (1, 3): 0/\0 0\/0, + sage: ps('0101', '0101') + [{(1, 1): 0/1\10, + (1, 2): 1/\1 10\/0, + (1, 3): 0/\0 1\/10, (1, 4): 1/\0 0\/1, - (2, 2): 1/1\1, - (2, 3): 0/\1 1\/0, + (2, 2): 0/0\0, + (2, 3): 10/\1 0\/0, (2, 4): 1/\1 1\/1, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, - (4, 4): 1/1\1}, {(1, 1): 0/0\0, + (4, 4): 1/1\1}, + {(1, 1): 0/0\0, (1, 2): 1/\0 0\/1, (1, 3): 0/\0 0\/0, (1, 4): 1/\0 0\/1, @@ -1652,12 +1804,13 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (2, 4): 1/\1 10\/0, (3, 3): 1/1\1, (3, 4): 0/\0 1\/10, - (4, 4): 10/0\1}, {(1, 1): 0/1\10, - (1, 2): 1/\1 10\/0, - (1, 3): 0/\0 1\/10, + (4, 4): 10/0\1}, + {(1, 1): 0/0\0, + (1, 2): 1/\0 0\/1, + (1, 3): 0/\0 0\/0, (1, 4): 1/\0 0\/1, - (2, 2): 0/0\0, - (2, 3): 10/\1 0\/0, + (2, 2): 1/1\1, + (2, 3): 0/\1 1\/0, (2, 4): 1/\1 1\/1, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, @@ -1666,18 +1819,18 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): K-Theory puzzles:: sage: ps = KnutsonTaoPuzzleSolver("K") - sage: solns = ps('0101', '0101') - sage: sorted(solns, key=str) - [{(1, 1): 0/0\0, - (1, 2): 1/\0 0\/1, - (1, 3): 0/\0 0\/0, + sage: ps('0101', '0101') + [{(1, 1): 0/1\10, + (1, 2): 1/\1 10\/0, + (1, 3): 0/\0 1\/K, (1, 4): 1/\0 0\/1, - (2, 2): 1/1\1, - (2, 3): 0/\10 1\/1, - (2, 4): 1/\1 10\/0, + (2, 2): 0/0\0, + (2, 3): K/\K 0\/1, + (2, 4): 1/\1 K\/0, (3, 3): 1/1\1, (3, 4): 0/\0 1\/10, - (4, 4): 10/0\1}, {(1, 1): 0/1\10, + (4, 4): 10/0\1}, + {(1, 1): 0/1\10, (1, 2): 1/\1 10\/0, (1, 3): 0/\0 1\/10, (1, 4): 1/\0 0\/1, @@ -1686,13 +1839,14 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (2, 4): 1/\1 1\/1, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, - (4, 4): 1/1\1}, {(1, 1): 0/1\10, - (1, 2): 1/\1 10\/0, - (1, 3): 0/\0 1\/K, + (4, 4): 1/1\1}, + {(1, 1): 0/0\0, + (1, 2): 1/\0 0\/1, + (1, 3): 0/\0 0\/0, (1, 4): 1/\0 0\/1, - (2, 2): 0/0\0, - (2, 3): K/\K 0\/1, - (2, 4): 1/\1 K\/0, + (2, 2): 1/1\1, + (2, 3): 0/\10 1\/1, + (2, 4): 1/\1 10\/0, (3, 3): 1/1\1, (3, 4): 0/\0 1\/10, (4, 4): 10/0\1}] @@ -1700,23 +1854,23 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): Two-step puzzles:: sage: ps = KnutsonTaoPuzzleSolver("H2step") - sage: solns = ps('01201', '01021') - sage: sorted(solns, key=str) - [{(1, 1): 0/0\0, - (1, 2): 1/\0 0\/1, - (1, 3): 2/\0 0\/2, - (1, 4): 0/\0 0\/0, + sage: ps('01201', '01021') + [{(1, 1): 0/2\20, + (1, 2): 1/\21 20\/0, + (1, 3): 2/\2 21\/1, + (1, 4): 0/\0 2\/20, (1, 5): 1/\0 0\/1, - (2, 2): 1/2\21, - (2, 3): 2/\2 21\/1, - (2, 4): 0/\10 2\/21, - (2, 5): 1/\1 10\/0, + (2, 2): 0/0\0, + (2, 3): 1/\0 0\/1, + (2, 4): 20/\2 0\/0, + (2, 5): 1/\1 2\/21, (3, 3): 1/1\1, - (3, 4): 21/\2 1\/1, - (3, 5): 0/\0 2\/20, - (4, 4): 1/1\1, - (4, 5): 20/\2 1\/10, - (5, 5): 10/0\1}, {(1, 1): 0/1\10, + (3, 4): 0/\0 1\/10, + (3, 5): 21/\0 0\/21, + (4, 4): 10/0\1, + (4, 5): 21/\2 1\/1, + (5, 5): 1/1\1}, + {(1, 1): 0/1\10, (1, 2): 1/\1 10\/0, (1, 3): 2/\1 1\/2, (1, 4): 0/\0 1\/10, @@ -1730,56 +1884,104 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (3, 5): 1/\0 2\/2(10), (4, 4): 0/0\0, (4, 5): 2(10)/\2 0\/1, - (5, 5): 1/1\1}, {(1, 1): 0/2\20, - (1, 2): 1/\21 20\/0, - (1, 3): 2/\2 21\/1, - (1, 4): 0/\0 2\/20, + (5, 5): 1/1\1}, + {(1, 1): 0/0\0, + (1, 2): 1/\0 0\/1, + (1, 3): 2/\0 0\/2, + (1, 4): 0/\0 0\/0, (1, 5): 1/\0 0\/1, - (2, 2): 0/0\0, - (2, 3): 1/\0 0\/1, - (2, 4): 20/\2 0\/0, - (2, 5): 1/\1 2\/21, + (2, 2): 1/2\21, + (2, 3): 2/\2 21\/1, + (2, 4): 0/\10 2\/21, + (2, 5): 1/\1 10\/0, (3, 3): 1/1\1, - (3, 4): 0/\0 1\/10, - (3, 5): 21/\0 0\/21, - (4, 4): 10/0\1, - (4, 5): 21/\2 1\/1, - (5, 5): 1/1\1}] + (3, 4): 21/\2 1\/1, + (3, 5): 0/\0 2\/20, + (4, 4): 1/1\1, + (4, 5): 20/\2 1\/10, + (5, 5): 10/0\1}] + Two-step equivariant puzzles:: sage: ps = KnutsonTaoPuzzleSolver("HT2step") - sage: solns = ps('10212', '12012') - sage: sorted(solns, key=str) - [{(1, 1): 1/1\1, - (1, 2): 0/\(21)0 1\/2, - (1, 3): 2/\1 (21)0\/0, - (1, 4): 1/\1 1\/1, + sage: ps('10212', '12012') + [{(1, 1): 1/2\21, + (1, 2): 0/\21 21\/0, + (1, 3): 2/\2 21\/1, + (1, 4): 1/\1 2\/21, (1, 5): 2/\1 1\/2, - (2, 2): 2/2\2, - (2, 3): 0/\2 2\/0, - (2, 4): 1/\2 2\/1, + (2, 2): 0/1\10, + (2, 3): 1/\1 10\/0, + (2, 4): 21/\2 1\/1, (2, 5): 2/\2 2\/2, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, (3, 5): 2/\0 0\/2, (4, 4): 1/1\1, (4, 5): 2/\1 1\/2, - (5, 5): 2/2\2}, {(1, 1): 1/1\1, - (1, 2): 0/\(21)0 1\/2, - (1, 3): 2/\1 (21)0\/0, - (1, 4): 1/\1 1\/1, + (5, 5): 2/2\2}, + {(1, 1): 1/2\21, + (1, 2): 0/\20 21\/1, + (1, 3): 2/\2 20\/0, + (1, 4): 1/\1 2\/21, (1, 5): 2/\1 1\/2, - (2, 2): 2/2\2, - (2, 3): 0/\2 2\/0, - (2, 4): 1/\21 2\/2, - (2, 5): 2/\2 21\/1, + (2, 2): 1/1\1, + (2, 3): 0/\10 1\/1, + (2, 4): 21/\2 10\/0, + (2, 5): 2/\2 2\/2, + (3, 3): 1/1\1, + (3, 4): 0/\0 1\/10, + (3, 5): 2/\0 0\/2, + (4, 4): 10/0\1, + (4, 5): 2/\1 1\/2, + (5, 5): 2/2\2}, + {(1, 1): 1/2\21, + (1, 2): 0/\20 21\/1, + (1, 3): 2/\2 20\/0, + (1, 4): 1/\1 2\/21, + (1, 5): 2/\1 1\/2, + (2, 2): 1/1\1, + (2, 3): 0/\1 1\/0, + (2, 4): 21/\2 1\/1, + (2, 5): 2/\2 2\/2, (3, 3): 0/0\0, - (3, 4): 2/\0 0\/2, - (3, 5): 1/\0 0\/1, + (3, 4): 1/\0 0\/1, + (3, 5): 2/\0 0\/2, + (4, 4): 1/1\1, + (4, 5): 2/\1 1\/2, + (5, 5): 2/2\2}, + {(1, 1): 1/1\1, + (1, 2): 0/\10 1\/1, + (1, 3): 2/\10 10\/2, + (1, 4): 1/\1 10\/0, + (1, 5): 2/\1 1\/2, + (2, 2): 1/2\21, + (2, 3): 2/\2 21\/1, + (2, 4): 0/\20 2\/2, + (2, 5): 2/\2 20\/0, + (3, 3): 1/1\1, + (3, 4): 2/\1 1\/2, + (3, 5): 0/\0 1\/10, (4, 4): 2/2\2, - (4, 5): 1/\1 2\/21, - (5, 5): 21/1\2}, {(1, 1): 1/1\1, + (4, 5): 10/\1 2\/20, + (5, 5): 20/0\2}, + {(1, 1): 1/1\1, + (1, 2): 0/\10 1\/1, + (1, 3): 2/\10 10\/2, + (1, 4): 1/\1 10\/0, + (1, 5): 2/\1 1\/2, + (2, 2): 1/2\21, + (2, 3): 2/\2 21\/1, + (2, 4): 0/\2 2\/0, + (2, 5): 2/\2 2\/2, + (3, 3): 1/1\1, + (3, 4): 0/\0 1\/10, + (3, 5): 2/\0 0\/2, + (4, 4): 10/0\1, + (4, 5): 2/\1 1\/2, + (5, 5): 2/2\2}, + {(1, 1): 1/1\1, (1, 2): 0/\(21)0 1\/2, (1, 3): 2/\1 (21)0\/0, (1, 4): 1/\1 1\/1, @@ -1793,13 +1995,29 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (3, 5): 1/\0 0\/1, (4, 4): 20/0\2, (4, 5): 1/\1 2\/21, - (5, 5): 21/1\2}, {(1, 1): 1/1\1, - (1, 2): 0/\1 1\/0, - (1, 3): 2/\1 1\/2, + (5, 5): 21/1\2}, + {(1, 1): 1/1\1, + (1, 2): 0/\(21)0 1\/2, + (1, 3): 2/\1 (21)0\/0, (1, 4): 1/\1 1\/1, (1, 5): 2/\1 1\/2, - (2, 2): 0/2\20, - (2, 3): 2/\2 20\/0, + (2, 2): 2/2\2, + (2, 3): 0/\2 2\/0, + (2, 4): 1/\21 2\/2, + (2, 5): 2/\2 21\/1, + (3, 3): 0/0\0, + (3, 4): 2/\0 0\/2, + (3, 5): 1/\0 0\/1, + (4, 4): 2/2\2, + (4, 5): 1/\1 2\/21, + (5, 5): 21/1\2}, + {(1, 1): 1/1\1, + (1, 2): 0/\(21)0 1\/2, + (1, 3): 2/\1 (21)0\/0, + (1, 4): 1/\1 1\/1, + (1, 5): 2/\1 1\/2, + (2, 2): 2/2\2, + (2, 3): 0/\2 2\/0, (2, 4): 1/\2 2\/1, (2, 5): 2/\2 2\/2, (3, 3): 0/0\0, @@ -1807,7 +2025,8 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (3, 5): 2/\0 0\/2, (4, 4): 1/1\1, (4, 5): 2/\1 1\/2, - (5, 5): 2/2\2}, {(1, 1): 1/1\1, + (5, 5): 2/2\2}, + {(1, 1): 1/1\1, (1, 2): 0/\1 1\/0, (1, 3): 2/\1 1\/2, (1, 4): 1/\1 1\/1, @@ -1821,70 +2040,15 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (3, 5): 1/\0 0\/1, (4, 4): 2/2\2, (4, 5): 1/\1 2\/21, - (5, 5): 21/1\2}, {(1, 1): 1/1\1, - (1, 2): 0/\10 1\/1, - (1, 3): 2/\10 10\/2, - (1, 4): 1/\1 10\/0, - (1, 5): 2/\1 1\/2, - (2, 2): 1/2\21, - (2, 3): 2/\2 21\/1, - (2, 4): 0/\2 2\/0, - (2, 5): 2/\2 2\/2, - (3, 3): 1/1\1, - (3, 4): 0/\0 1\/10, - (3, 5): 2/\0 0\/2, - (4, 4): 10/0\1, - (4, 5): 2/\1 1\/2, - (5, 5): 2/2\2}, {(1, 1): 1/1\1, - (1, 2): 0/\10 1\/1, - (1, 3): 2/\10 10\/2, - (1, 4): 1/\1 10\/0, - (1, 5): 2/\1 1\/2, - (2, 2): 1/2\21, - (2, 3): 2/\2 21\/1, - (2, 4): 0/\20 2\/2, - (2, 5): 2/\2 20\/0, - (3, 3): 1/1\1, - (3, 4): 2/\1 1\/2, - (3, 5): 0/\0 1\/10, - (4, 4): 2/2\2, - (4, 5): 10/\1 2\/20, - (5, 5): 20/0\2}, {(1, 1): 1/2\21, - (1, 2): 0/\20 21\/1, - (1, 3): 2/\2 20\/0, - (1, 4): 1/\1 2\/21, - (1, 5): 2/\1 1\/2, - (2, 2): 1/1\1, - (2, 3): 0/\1 1\/0, - (2, 4): 21/\2 1\/1, - (2, 5): 2/\2 2\/2, - (3, 3): 0/0\0, - (3, 4): 1/\0 0\/1, - (3, 5): 2/\0 0\/2, - (4, 4): 1/1\1, - (4, 5): 2/\1 1\/2, - (5, 5): 2/2\2}, {(1, 1): 1/2\21, - (1, 2): 0/\20 21\/1, - (1, 3): 2/\2 20\/0, - (1, 4): 1/\1 2\/21, - (1, 5): 2/\1 1\/2, - (2, 2): 1/1\1, - (2, 3): 0/\10 1\/1, - (2, 4): 21/\2 10\/0, - (2, 5): 2/\2 2\/2, - (3, 3): 1/1\1, - (3, 4): 0/\0 1\/10, - (3, 5): 2/\0 0\/2, - (4, 4): 10/0\1, - (4, 5): 2/\1 1\/2, - (5, 5): 2/2\2}, {(1, 1): 1/2\21, - (1, 2): 0/\21 21\/0, - (1, 3): 2/\2 21\/1, - (1, 4): 1/\1 2\/21, + (5, 5): 21/1\2}, + {(1, 1): 1/1\1, + (1, 2): 0/\1 1\/0, + (1, 3): 2/\1 1\/2, + (1, 4): 1/\1 1\/1, (1, 5): 2/\1 1\/2, - (2, 2): 0/1\10, - (2, 3): 1/\1 10\/0, - (2, 4): 21/\2 1\/1, + (2, 2): 0/2\20, + (2, 3): 2/\2 20\/0, + (2, 4): 1/\2 2\/1, (2, 5): 2/\2 2\/2, (3, 3): 0/0\0, (3, 4): 1/\0 0\/1, @@ -1893,7 +2057,6 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (4, 5): 2/\1 1\/2, (5, 5): 2/2\2}] - Belkale-Kumar puzzles (the following example is Figure 2 of [KnutsonPurbhoo10]_):: sage: ps = KnutsonTaoPuzzleSolver('BK', 3) @@ -1920,50 +2083,9 @@ class KnutsonTaoPuzzleSolver(UniqueRepresentation): (5, 5): 2(1)/1\2}] """ - def __init__(self, puzzle_pieces): + def __init__(self, puzzle_pieces, max_letter=None): r""" Knutson-Tao puzzle solver. - - TESTS: - - Check that UniqueRepresentation works:: - - sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver, H_grassmannian_pieces - sage: ps = KnutsonTaoPuzzleSolver(H_grassmannian_pieces()) - sage: qs = KnutsonTaoPuzzleSolver("H") - sage: ps - Knutson-Tao puzzle solver with pieces: - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] - sage: qs - Knutson-Tao puzzle solver with pieces: - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] - sage: ps == qs - True - """ - self._puzzle_pieces = puzzle_pieces - self._rhombus_pieces = tuple(puzzle_pieces.rhombus_pieces()) - self._bottom_deltas = tuple(puzzle_pieces.boundary_deltas()) - - @staticmethod - def __classcall_private__(cls, puzzle_pieces, max_letter=None): - r""" - TESTS:: - - sage: from sage.combinat.knutson_tao_puzzles import * - sage: KnutsonTaoPuzzleSolver(H_grassmannian_pieces()) == KnutsonTaoPuzzleSolver("H") # indirect doctest - True - sage: KnutsonTaoPuzzleSolver(HT_grassmannian_pieces()) == KnutsonTaoPuzzleSolver("HT") - True - sage: KnutsonTaoPuzzleSolver(K_grassmannian_pieces()) == KnutsonTaoPuzzleSolver("K") - True - sage: KnutsonTaoPuzzleSolver(H_two_step_pieces()) == KnutsonTaoPuzzleSolver("H2step") - True - sage: KnutsonTaoPuzzleSolver(HT_two_step_pieces()) == KnutsonTaoPuzzleSolver("HT2step") - True - sage: KnutsonTaoPuzzleSolver(BK_pieces(3)) == KnutsonTaoPuzzleSolver("BK",3) - True """ if isinstance(puzzle_pieces, str): if puzzle_pieces == "H": @@ -1981,9 +2103,11 @@ def __classcall_private__(cls, puzzle_pieces, max_letter=None): puzzle_pieces = BK_pieces(max_letter) else: raise ValueError("max_letter needs to be specified") - return super().__classcall__(cls, puzzle_pieces) + self._puzzle_pieces = puzzle_pieces + self._rhombus_pieces = puzzle_pieces.rhombus_pieces() + self._bottom_deltas = puzzle_pieces.boundary_deltas() - def __call__(self, lamda, mu, algorithm='strips'): + def __call__(self, lam, mu, algorithm='strips'): r""" TESTS:: @@ -2012,11 +2136,11 @@ def __call__(self, lamda, mu, algorithm='strips'): (3, 4): 0/\0 1\/10, (4, 4): 10/0\1}] """ - lamda, mu = tuple(lamda), tuple(mu) + lam, mu = tuple(lam), tuple(mu) if algorithm == 'pieces': - return list(self._fill_puzzle_by_pieces(lamda, mu)) + return list(self._fill_puzzle_by_pieces(lam, mu)) elif algorithm == 'strips': - return list(self._fill_puzzle_by_strips(lamda, mu)) + return list(self._fill_puzzle_by_strips(lam, mu)) solutions = __call__ @@ -2027,8 +2151,8 @@ def __repr__(self) -> str: sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver sage: KnutsonTaoPuzzleSolver('H') Knutson-Tao puzzle solver with pieces: - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) """ return "Knutson-Tao puzzle solver with pieces:\n%s" % self._puzzle_pieces @@ -2041,8 +2165,9 @@ def puzzle_pieces(self): sage: from sage.combinat.knutson_tao_puzzles import KnutsonTaoPuzzleSolver sage: ps = KnutsonTaoPuzzleSolver('H') sage: ps.puzzle_pieces() - Nablas : [0\0/0, 0\10/1, 10\1/0, 1\0/10, 1\1/1] - Deltas : [0/0\0, 0/1\10, 1/10\0, 1/1\1, 10/0\1] + Nablas : (0\0/0, 1\1/1, 10\1/0, 0\10/1, 1\0/10) + Deltas : (0/0\0, 1/1\1, 0/1\10, 1/10\0, 10/0\1) + """ return self._puzzle_pieces @@ -2118,7 +2243,7 @@ def _fill_strip(self, nw_labels, ne_label, pieces, final_pieces=None): output.append(partial_filling + [piece]) return output - def _fill_puzzle_by_pieces(self, lamda, mu): + def _fill_puzzle_by_pieces(self, lam, mu): r""" Fill puzzle pieces for given outer labels ``lambda`` and ``mu``. @@ -2129,7 +2254,7 @@ def _fill_puzzle_by_pieces(self, lamda, mu): sage: list(ps._fill_puzzle_by_pieces('0', '0')) [{(1, 1): 0/0\0}] """ - queue = [PuzzleFilling(lamda, mu)] + queue = [PuzzleFilling(lam, mu)] while queue: PP = queue.pop() ne_label = PP.north_east_label_of_kink() @@ -2146,7 +2271,7 @@ def _fill_puzzle_by_pieces(self, lamda, mu): else: queue.append(PPcopy) - def _fill_puzzle_by_strips(self, lamda, mu): + def _fill_puzzle_by_strips(self, lam, mu): r""" Fill puzzle pieces by strips for given outer labels ``lambda`` and ``mu``. @@ -2159,7 +2284,7 @@ def _fill_puzzle_by_strips(self, lamda, mu): sage: list(ps._fill_puzzle_by_strips('01', '01')) [{(1, 1): 0/0\0, (1, 2): 1/\0 0\/1, (2, 2): 1/1\1}] """ - queue = [PuzzleFilling(lamda, mu)] + queue = [PuzzleFilling(lam, mu)] while queue: PP = queue.pop() i, _ = PP.kink_coordinates() @@ -2169,7 +2294,7 @@ def _fill_puzzle_by_strips(self, lamda, mu): nw_labels = PP._nw_labels else: nw_labels = tuple(PP._squares[i - 1, k]['south_east'] - for k in range(i, len(lamda) + 1)) + for k in range(i, len(lam) + 1)) # grab ne labels ne_label = PP._ne_labels[i - 1] @@ -2203,7 +2328,7 @@ def plot(self, puzzles): m = len([gg.axes(False) for gg in g]) return graphics_array(g, (m + 3) / 4, 4) - def structure_constants(self, lamda, mu, nu=None): + def structure_constants(self, lam, mu, nu=None): r""" Compute cohomology structure coefficients from puzzles. @@ -2236,7 +2361,7 @@ def structure_constants(self, lamda, mu, nu=None): sage: ps = KnutsonTaoPuzzleSolver('HT') sage: cp = ps.structure_constants('0101', '0101') - sage: sorted(cp.items(), key=str) + sage: sorted(cp.items()) [(('0', '1', '0', '1'), y2 - y3), (('0', '1', '1', '0'), 1), (('1', '0', '0', '1'), 1)] @@ -2245,17 +2370,17 @@ def structure_constants(self, lamda, mu, nu=None): sage: ps = KnutsonTaoPuzzleSolver('K') sage: cp = ps.structure_constants('0101', '0101') - sage: sorted(cp.items(), key=str) + sage: sorted(cp.items()) [(('0', '1', '1', '0'), 1), (('1', '0', '0', '1'), 1), (('1', '0', '1', '0'), -1)] Two-step:: sage: ps = KnutsonTaoPuzzleSolver('H2step') sage: cp = ps.structure_constants('01122', '01122') - sage: sorted(cp.items(), key=str) + sage: sorted(cp.items()) [(('0', '1', '1', '2', '2'), 1)] sage: cp = ps.structure_constants('01201', '01021') - sage: sorted(cp.items(), key=str) + sage: sorted(cp.items()) [(('0', '2', '1', '1', '0'), 1), (('1', '2', '0', '0', '1'), 1), (('2', '0', '1', '0', '1'), 1)] @@ -2264,7 +2389,7 @@ def structure_constants(self, lamda, mu, nu=None): sage: ps = KnutsonTaoPuzzleSolver('HT2step') sage: cp = ps.structure_constants('10212', '12012') - sage: sorted(cp.items(), key=str) + sage: sorted(cp.items()) [(('1', '2', '0', '1', '2'), y1*y2 - y2*y3 - y1*y4 + y3*y4), (('1', '2', '0', '2', '1'), y1 - y3), (('1', '2', '1', '0', '2'), y2 - y4), @@ -2274,9 +2399,9 @@ def structure_constants(self, lamda, mu, nu=None): (('2', '1', '1', '0', '2'), 1)] """ from collections import defaultdict - R = PolynomialRing(Integers(), 'y', len(lamda) + 1) + R = PolynomialRing(Integers(), 'y', len(lam) + 1) z = defaultdict(R.zero) - for p in self(lamda, mu): + for p in self(lam, mu): z[p.south_labels()] += p.contribution() if nu is None: return dict(z) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index dd2ef6fe63e..c898256e98b 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -62,6 +62,8 @@ - Matthew Lancellotti (2018-09-14): Added a bunch of "k" methods to Partition. +- Álvaro Gutiérrez (2025-02-24): added functionality to translate to and from abaci + EXAMPLES: There are `5` partitions of the integer `4`:: @@ -274,8 +276,12 @@ """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , +# 2025 Álvaro Gutiérrez # -# Distributed under the terms of the GNU General Public License (GPL) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** @@ -5809,6 +5815,62 @@ def tabloid_module(self, base_ring=None): R = SymmetricGroupAlgebra(base_ring, sum(self)) return TabloidModule(R, self) + def to_abacus(self, size=0, ones=0): + r""" + Return an abacus. + + This is the inverse to :func:`.abacus_to_partition`. + + An abacus is a function `w : \mathbb{Z} \to \{0,1\}` such that + `w(n) = 0` for `n \ll 0` and `w(n) = 1` for `n \gg 0`. It is usually + represented with an infinite tuple, e.g. `(...,0,0,0,1,0,0,1,0,1,1,...)`. + Here we use finite tuples and assume everything to the left is a 0 + and everything to the right is a 1. + + A partition determines an abacus via the following interpretation: + read the outline of the Young diagram of the partition, with English + convention. For each vertical step, record a 1; for each horizontal + step record a 0. The resulting word is the corresponding abacus. + + The abacus will be of length at least ``size``, by padding with + 0s on the right if necessary. The number of 1s in the abacus will + be at least ``ones``, by padding with 1s on the left if necessary. + + INPUT: + + - ``size``, ``ones`` -- Integer (optional. Default: 0) + + OUTPUT: + + - a string of 0s and 1s. + + EXAMPLES:: + + sage: Partition([3,2,1]).to_abacus() + '010101' + sage: Partition([3,2,1]).to_abacus(size=10) + '0101010000' + sage: Partition([3,3]).to_abacus() + '00011' + sage: Partition([]).to_abacus(size=6) + '000000' + sage: Partition([]).to_abacus(ones=2) + '11' + sage: Partition([2,2]).to_abacus(size=4) + '0011' + sage: Partition([2,2]).to_abacus(size=5) + '00110' + sage: Partition([2,2]).to_abacus(size=5, ones=3) + '10011' + + """ + L = max(ones,len(self)) + lam = list(self)+[0]*(L+1-len(self)) + abacus = [1] * L + for i in range(L): + abacus = abacus[:(L-i-1)] + [0]*(lam[i]-lam[i+1]) + abacus[(L-i-1):] + return ''.join(map(str, abacus + [0]*(size-len(abacus)))) + ############## # Partitions # @@ -9665,6 +9727,63 @@ def _an_element_(self): """ return self.element_class(self, Partitions_n._an_element_(self).conjugate()) +################################################################## + + +def abacus_to_partition(abacus): + r""" + Return a partition from an abacus. + + An abacus is a function `w : \mathbb{Z} \to \{0,1\}` such that + `w(n) = 0` for `n \ll 0` and `w(n) = 1` for `n \gg 0`. It is usually + represented with an infinite tuple, e.g. `(...,0,0,0,1,0,0,1,0,1,1,...)`. + Here, we use finite tuples, and assume everything to the left is a 0 + and everything to the right is a 1. + + An abacus determines a partition, via the following interpretation: + a 1 in the abacus encodes a vertical line, and a 0 encodes a + horizontal one. Reading from left to right, the abacus spells the + outline of the Young diagram. + + This is the inverse to :meth:`Partition.to_abacus`. + + INPUT: + + - ``abacus`` -- a tuple, a list or a string of 1's and 0's + + OUTPUT: + + - a Partition + + EXAMPLES:: + + sage: from sage.combinat.partition import abacus_to_partition + sage: abacus_to_partition('010101') + [3, 2, 1] + sage: abacus_to_partition([1,1,0,0,0]) + [] + sage: abacus_to_partition((0,0,0,1,1)) + [3, 3] + sage: abacus_to_partition(('1','1','1','0','0','0')) + [] + sage: abacus_to_partition(['0','0','0','1','1','1']) + [3, 3, 3] + sage: abacus_to_partition('--*-**') + Traceback (most recent call last): + ... + ValueError: an abacus should be a tuple, list or string of 0s and 1s + """ + part = [] + n = len(abacus) + k = 0 + for i in range(n): + if abacus[i] == '1' or abacus[i] == 1: + k += 1 + part.insert(0, i+1-k) + elif abacus[i] != '0' and abacus[i] != 0: + raise ValueError('an abacus should be a tuple, list or string of 0s and 1s') + return Partition(part) + ######################################################################### diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 2d35efc2363..b09046e6d72 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -7,21 +7,19 @@ - Travis Scrimshaw, Arthur Lubovsky (2013-02-11): Factored out ``CombinatorialClass`` - Trevor K. Karn (2022-08-03): added ``backward_slide`` +- Álvaro Gutiérrez (2025-02-24): added ``to_knutson_tao_puzzle`` + """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , -# Copyright (C) 2013 Travis Scrimshaw -# Copyright (C) 2013 Arthur Lubovsky -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: +# 2013 Travis Scrimshaw +# 2013 Arthur Lubovsky +# 2025 Álvaro Gutiérrez # +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** @@ -1844,6 +1842,169 @@ def is_k_tableau(self, k) -> bool: return all(kshapes[i + 1].contains(kshapes[i]) for i in range(len(shapes) - 1)) + def is_littlewood_richardson(self): + r""" + Check whether ``self`` is a Littlewood--Richardson tableau. + + A Littlewood--Richardson tableau is a semistandard skew tableau whose + (row reading) word is Yamanouchi. + + EXAMPLES:: + + sage: SkewTableau([[None,1],[1,2]]).is_littlewood_richardson() + True + sage: SkewTableau([[None,1],[2,1]]).is_littlewood_richardson() + False + sage: SkewTableau([[None,1],[2,2]]).is_littlewood_richardson() + False + """ + return self.is_semistandard() and self.to_word().is_yamanouchi() + + def to_knutson_tao_puzzle(self, size=None): + r""" + Return a Knutson--Tao puzzle. + + INPUT: + + - ``size`` -- the size of the output Knutson--Tao puzzle (optional) + + EXAMPLES:: + + sage: ps = KnutsonTaoPuzzleSolver("H") + sage: puzzle = ps('01010','01001')[0] + sage: tab = puzzle.to_littlewood_richardson_tableau(); tab + [[None, 1, 1], [2]] + sage: puzzle2 = tab.to_knutson_tao_puzzle() + sage: puzzle == puzzle2 + True + + TESTS:: + + sage: # Example from Purbhoo07 + sage: tab = SkewTableau([ + ....: [None]*10 + [1]*4, + ....: [None]*8 + [1]*2 + [2]*3, + ....: [None]*5 + [1]*2 + [2]*2 + [3]*2, + ....: [None] + [1]*2 + [2]*2 + [3] + [4]*2]) + sage: puzzle = tab.to_knutson_tao_puzzle(20) + sage: puzzle[(5,10)] + 1/\0 0\/1 + sage: ''.join(puzzle.south_labels()) + '01000010001001000000' + sage: puzzle.plot() # not tested + """ + assert self.is_littlewood_richardson(), "this method only applies to Littlewood-Richardson tableaux" + + from sage.combinat.partition import abacus_to_partition + from sage.combinat.knutson_tao_puzzles import H_grassmannian_pieces, KnutsonTaoPuzzleSolver, PuzzleFilling + + # Extract border of puzzle from tableau + + tab_w = self.weight() + tab_out = self.outer_shape() + tab_inn = self.inner_shape() + + if size is None: + size = max(tab_w[0] + len(self), tab_out[0] + len(self)) + assert size >= max(tab_w[0] + len(self), tab_out[0] + len(self)), "the puzzle size inputted is too small" + + lam = Partition(tab_w).to_abacus(size=size, ones=len(self))[::-1] + mu = [(size - len(self)) - r for r in tab_out][::-1] + mu = Partition(mu).to_abacus(size=size, ones=len(self))[::-1] + nu = Partition(tab_inn).to_abacus(size=size, ones=len(self)) + + # Initialize puzzle + + puzzle = PuzzleFilling(lam, mu) + + # Find the locations of 1-triangles (blue by default in the plot) + + chosenCols = [i+1 for i in range(size) if nu[i] == '1'] + chosenRows = [i+1 for i in range(size) if lam[i] == '1'] + delta_blue_positions = [] + nabla_blue_positions = [] + for col in range(len(self)): + propagationRow = [] + propagationCol = [] + for row in range(col+1): + i = chosenRows[row] + j = chosenCols[col-row] + + delta_blue_positions.append((j, i)) + + k = self[len(self)-col+row-1].count(row+1) + nabla_blue_positions.append((j+k, i+k+1)) + + propagationCol.insert(0, j+k) + propagationRow.insert(0, i+k+1) + chosenRows = sorted(propagationRow) + chosenRows[col+1:] + chosenCols = sorted(propagationCol) + chosenCols[col+1:] + + # Create a dictionary of boundaries + # (not all boundaries are in the dictionary) + + D = {(i,j) : {} for i in range(1,size+1) for j in range(i,size+1)} + for i,j in delta_blue_positions: + D[(i,j)]['north_west'] = '1' + D[(i,j)]['north_east'] = '1' + a, b = i, j + while ((a-1, b) not in nabla_blue_positions and a > 1): + a -= 1 + D[(a,b)]['north_east'] = '0' + D[(a,b)]['north_west'] = '1' + D[(a,b)]['south_east'] = '1' + D[(a,b)]['south_west'] = '0' + a, b = i, j + while ((a, b) not in nabla_blue_positions and b > a): + b -= 1 + D[(a,b)]['north_west'] = '0' + D[(a,b)]['north_east'] = '10' + for i, j in nabla_blue_positions: + if (i,j) in D: + D[(i,j)]['south_west'] = '1' + D[(i,j)]['south_east'] = '1' + a, b = i, j + while ((a, b-1) not in delta_blue_positions and a > 0): + a -= 1 + b -= 1 + D[(a,b)]['south_east'] = '10' + D[(a,b)]['south_west'] = '1' + D[(a+1,b)]['north_east'] = '1' + D[(a+1,b)]['north_west'] = '10' + for i in range(size): + D[(1,i+1)]['north_west'] = lam[i] + D[(i+1,i+1)]['south'] = nu[i] + D[(i+1,size)]['north_east'] = mu[i] + + # Now fill piece by piece + # (like KnutsonTaoPuzzleSolver._fill_puzzle_by_pieces + # but with the extra constraints given by the above) + + all_pieces = H_grassmannian_pieces() + dirs = ('north_east', 'north_west', 'south_east', 'south_west') + rhombi = sorted(all_pieces.rhombus_pieces(), key=lambda p : ''.join(p[dir] for dir in dirs)) + # the sorting guarantees that the 0/\0 0\/0 piece is always + # inserted if possible. + dirs = ('north_east', 'north_west', 'south') + triangles = sorted(all_pieces.delta_pieces(), key=lambda p : ''.join(p[dir] for dir in dirs)) + + for i, j in D: + candidates = [] + if i == j: + pieces = triangles + dirs = ('north_east', 'north_west', 'south') + else: + pieces = rhombi + dirs = ('north_east', 'north_west', 'south_east', 'south_west') + # Check what pieces have the desired boundaries + for piece in pieces: + if all(piece[dir] == D[(i,j)][dir] for dir in dirs if dir in D[(i,j)]): + candidates.append(piece) + # Place the smallest possible piece (the one with most 0s) + puzzle._squares[(i,j)] = candidates[0] + + return puzzle + def _label_skew(list_of_cells, sk): """