Source code for skew_partition

# -*- coding: utf-8 -*-
r"""
Sage has a builtin `SkewPartition <https://doc.sagemath.org/html/en/reference/combinat/sage/combinat/skew_partition.html>`_ object (a.k.a. skew-shape).  *This* module adds extra useful functions for skew partitions:

AUTHORS:

- Matthew Lancellotti (2018): Initial version

REFERENCES:

.. [mem] Lam, T., Lapointe, L., Morse, J., & Shimozono, M. (2013). `The poset of k-shapes and branching rules for k-Schur functions <http://breakfreerun.org/index.php/ebooks/the-poset-of-k-shapes-and-branching-rules-for-k-schur-functions>`_. Memoirs of the American Mathematical Society, 223(1050), 1-113. DOI: 10.1090/S0065-9266-2012-00655-1
"""

#*****************************************************************************
#  Copyright (C) 2018 Matthew Lancellotti <mvlancellotti@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#                  http://www.gnu.org/licenses/
#*****************************************************************************


from sage.all import *
from partition import is_weakly_decreasing
SetPartitionsAk = None
SetPartitionsBk = None
SetPartitionsIk = None
SetPartitionsPRk = None
SetPartitionsPk = None
SetPartitionsRk = None
SetPartitionsSk = None
SetPartitionsTk = None



# SkewPartition stuff


[docs]def is_symmetric(sp): r""" A SkewPartition is *symmetric* if its inner and outer shapes are symmetric. Return ``True`` if and only if the :class:`SkewPartition` ``sp`` is equal to its own conjugate. EXAMPLES:: sage: SkewPartition([[], []]).is_symmetric() sage: True sage: SkewPartition([[1], []]).is_symmetric() sage: True sage: SkewPartition([[4, 3, 3, 1], [1]]).is_symmetric() sage: True sage: SkewPartition([[4, 3, 3, 1], [1, 1]]).is_symmetric() sage: False sage: SkewPartition([[5, 3, 3, 1], [2, 2]]).is_symmetric() sage: False .. SEEALSO:: :meth:`Partition.is_symmetric` """ return sp == sp.conjugate()
[docs]def left(sp, row_index): r""" Given a SkewPartition and a 0-based row index, return the 0-based column index of the *leftmost* cell in the corresponding row. (Section 2.1 of [mem]_) EXAMPLES:: sage: left(SkewPartition([[4, 1], [2]]), 0) 2 sage: left(SkewPartition([[4, 1], [2]]), 1) 0 An input index that is out of the bounds of the skew partition will throw an error:: sage: left(SkewPartition([[4, 1], [2]]), 2) IndexError: list index out of range An in-bounds index where no cells exist will return ``None``:: sage: left(SkewPartition([[2, 1, 1, 1], [1, 1]]), 1) == None True .. SEEALSO:: :meth:`right`, :meth:`top`, :meth:`bottom` """ # first check to make sure the cell exists if sp.row_lengths()[row_index] == 0: return None outer_row_lengths = sp.outer().to_list() inner_row_lengths = sp.inner().to_list() if (row_index in range(len(outer_row_lengths))) and (row_index not in range(len(inner_row_lengths))): inner_row_length = 0 else: inner_row_length = inner_row_lengths[row_index] col_index = inner_row_length return col_index
[docs]def top(sp, col_index): r""" Given a SkewPartition and a 0-based column index, return the 0-based row index of the *topmost* cell in the corresponding column. (Section 2.1 of [mem]_) EXAMPLES:: sage: top(SkewPartition([[4, 1, 1], [2]]), 0) 2 An in-bounds col_index where no cells exist will return ``None``:: sage: top(SkewPartition([[4, 1, 1], [2]]), 1) == None True sage: top(SkewPartition([[4, 1, 1], [2]]), 2) 0 sage: top(SkewPartition([[4, 1, 1], [2]]), 3) 0 A col_index that is out-of-bounds of the skew partition will throw an error:: sage: top(SkewPartition([[4, 1, 1], [2]]), 4) IndexError: list index out of range .. SEEALSO:: :meth:`right`, :meth:`left`, :meth:`bottom` """ return right(sp.conjugate(), col_index)
[docs]def bottom(sp, col_index): r""" Given a SkewPartition and a 0-based column index, return the 0-based row index of the *bottommost* cell in the corresponding column. (Section 2.1 of [mem]_) EXAMPLES:: sage: bottom(SkewPartition([[4, 1, 1], [2]]), 0) 1 An in-bounds col_index where no cells exist will return ``None``:: sage: bottom(SkewPartition([[4, 1, 1], [2]]), 1) == None True sage: bottom(SkewPartition([[4, 1, 1], [2]]), 2) 0 sage: bottom(SkewPartition([[4, 1, 1], [2]]), 3) 0 A col_index that is out-of-bounds of the skew partition will throw an error:: sage: bottom(SkewPartition([[4, 1, 1], [2]]), 4) IndexError: list index out of range .. SEEALSO:: :meth:`right`, :meth:`left`, :meth:`top` """ return left(sp.conjugate(), col_index)
[docs]def is_linked(sp): r""" A skew-shape ``sp`` is a *skew-linked diagram* if both the row-shape and column-shape of `sp` are partitions. EXAMPLES: Both row shape and column shape are valid:: sage: is_linked(SkewPartition([[2, 1], [1]])) True Valid row shape but invalid column shape:: sage: is_linked(SkewPartition([[3, 2], [1]])) False .. SEEALSO:: :meth:`row_lengths`, :meth:`column_lengths` """ return is_weakly_decreasing(sp.row_lengths()) and is_weakly_decreasing(sp.column_lengths())
def row_col_to_skew_partition(rs, cs): # this ALREADY exists in sage. see SkewPartition.from_row_and_column_length outer = [] inner = [] current_cs = [0] * len(cs) row_index = 0 for col_coindex, col_length in enumerate(list(reversed(cs))): current_col_length = list(reversed(current_cs))[col_coindex] num_rows_to_slide = col_length - current_col_length if num_rows_to_slide < 0: raise ValueError( 'The inputted (row-shape, col-shape) pair has no possible corresponding skew-shape.') # 'col_num' is 1-based index of cols col_num = len(cs) - col_coindex while num_rows_to_slide > 0: if row_index > len(rs) - 1: raise ValueError('error more') # slide a row outer.append(col_num) inner.append(col_num - rs[row_index]) # update params/info for c in range(col_num - rs[row_index], col_num): current_cs[c] += 1 row_index += 1 num_rows_to_slide -= 1 return SkewPartition([outer, inner])
[docs]def k_boundary_to_partition(sp, k=None, check=True): r""" Given a ``k``-boundary ``sp`` (`k`-boundaries are a specific type of skew-shape), output the original partition whose `k`-boundary is ``sp``. (For the definition of `k`-boundary, see Section 2.2 of [mem]_) If ``check`` is set to ``True``, the program will assert that the skew-shape really is a `k`-boundary. TODO: test EXAMPLES:: sage: k_boundary_to_partition(SkewPartition([[3, 2, 1], [2, 1]])) [3, 2, 1] sage: k_boundary_to_partition(SkewPartition([[3, 1], [2]]), k=2) Error sage: k_boundary_to_partition(SkewPartition([[3, 1], [2]]), check=False) [3, 1] .. SEEALSO:: :meth:`is_k_boundary`, :meth:`outer` """ if check: assert is_k_boundary(sp, k) return sp.outer()
[docs]def is_k_boundary(sp, k=None): r""" Given a skew-shape ``sp`` and natural number ``k``, return ``True`` if and only if ``sp`` is a `k`-boundary. (Section 2.2 of [mem]_) Given a skew-shape ``sp`` *only*, return ``True`` if and only if there exists some `k` such that ``sp`` is a `k`-boundary. TODO: test EXAMPLES:: sage: is_k_boundary(SkewPartition([[3, 2, 1], [2, 1]])) True sage: is_k_boundary(SkewPartition([[3, 2, 1], [2, 1]]), k=1) True sage: is_k_boundary(SkewPartition([[3, 2, 1], [2, 1]]), k=2) False .. SEEALSO:: :meth:`Partition.k_boundary` """ if k is None: max_hook_length = sp.outer().hook_length(0, 0) return any(is_k_boundary(sp, k_star) for k_star in range(0, max_hook_length+1)) elif k == 0: # the only valid 0-boundary is the empty shape return sp.outer() == sp.inner() else: r"""We go down and left of each cell to create the only possible partition that could have led to this potential k-boundary (Any other partition containing this skew_shape would necessarily have a northeast corner that the skew_shape does *not have*. But in order for the skew-shape to be a k-boundary, it *must have* that northeast corner.) """ l = k_boundary_to_partition(sp, check=False) r"""now that we have the partition, we simply compute it's hook-length for each cell and verify that for each cell of values k or less, it appears in the sp""" correct_k_boundary = l.k_boundary(k) return sp == correct_k_boundary
[docs]def row_shape_to_linked_skew_partitions(rs): r""" Given a partition ``rs``, find all linked SkewPartitions whose row-shape is ``rs``. EXAMPLES: Note that [4, 2, 1] / [1, 1] is *not* linked and hence doesn't appear in the list below:: sage: row_shape_to_linked_skew_partitions(Partition([3, 1, 1])) [[3, 1, 1] / [], [4, 1, 1] / [1], [5, 2, 1] / [2, 1]] .. SEEALSO:: :meth:`is_linked`, :meth:`row_lengths` """ def ptn_to_linked_things(p): def thing_to_added_row_things(sp, row_len): def add_row(sp, row_len, offset): # add the next row onto the skew shape outer = sp.outer().to_list() inner = sp.inner().to_list() inner += [0] * (len(outer) - len(inner)) outer = [e + offset for e in outer] inner = [e + offset for e in inner] outer.append(row_len) return SkewPartition([outer, inner]) # START thing_to_added_row_things previous_checked_col_index = sp.outer()[-1] # find the maximum leftmost offset for the new row col_lens = sp.column_lengths() max_offset = row_len prev_col_len = 0 for col_index in range(previous_checked_col_index, -1, -1): # get length of column col_len = col_lens[col_index] if col_index < len(col_lens) else 0 # check col-shape partition condition if col_len >= prev_col_len: # col_index is good, continue prev_col_len = col_len else: # col_index is bad, stop good_col_index = col_index + 1 max_offset = row_len - good_col_index break # now add all possible positions for the row onto the list return [add_row(sp, row_len, offset) for offset in range(0, max_offset+1)] # START ptn_to_linked_things assert isinstance(p, list) and not isinstance(p, Partition) if len(p) <= 1: return [SkewPartition([p, []])] else: # these incomplete guys are not necessarily skew partitions incomplete_things = ptn_to_linked_things(p[:-1]) almost_complete_things = [] for incomplete_thing in incomplete_things: almost_complete_things += thing_to_added_row_things( incomplete_thing, p[-1]) return almost_complete_things # START row_shape_to_linked_skew_partitions rs_zero = list(Partition(rs)) + [0] return ptn_to_linked_things(rs_zero)
[docs]def size_to_linked_skew_partitions(size): r""" Given a natural number ``size``, return all linked SkewPartitions of size ``size``. EXAMPLES:: sage: size_to_linked_skew_partitions(3) [[3] / [], [2, 1] / [], [3, 1] / [1], [1, 1, 1] / [], [2, 1, 1] / [1], [3, 2, 1] / [2, 1]] .. SEEALSO:: :meth:`is_linked`, :meth:`size` """ linked_skew_ptns = [] # Here is one major optimization that's possible: Instead of first calculating all Partitions(size), and then doing the ptn_to_linked_things algo for each partition, actually go through the work of generating the partitions manually, and use ptn_to_linked_things algo as you go. This is to ELIMINATE the redundancy of having two partitions that START with the same sub-partition. ptns = Partitions(size) for ptn in ptns: linked_skew_ptns += row_shape_to_linked_skew_partitions(ptn) return linked_skew_ptns