Source code for root_ideal

# -*- coding: utf-8 -*-
r"""
Sage does *not* have a builtin 'RootIdeal' object.  *This* module contains a RootIdeal class and useful functions pertaining to root ideals:

AUTHORS:

- Matthew Lancellotti (2018): Initial version

REFERENCES:

.. [cat] `Catalan functions and k-schur positivity <https://arxiv.org/abs/1804.03701>`_
.. [scat] Skew-linked Catalan functions and k-schur positivity.  Jonah Blasiak, Jennifer Morse, Anna Pun, and Daniel Summers.  Not to be confused with 'Catalan functions and k-schur positivity.'
"""

#*****************************************************************************
#  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 *
import partition
import skew_partition
SetPartitionsAk = None
SetPartitionsBk = None
SetPartitionsIk = None
SetPartitionsPRk = None
SetPartitionsPk = None
SetPartitionsRk = None
SetPartitionsSk = None
SetPartitionsTk = None


# HELPERS


[docs]def is_pseudo_partition(seq): r""" Return whether ``seq`` is a pseudo-partition. A __pseudo partition__ is a composition `\gamma` such that `\gamma + \rho` is a partition, where `\rho` is the staircase shape. Another way to think of this function is that it will return ``False`` if the "slope" ever exceeds `+1`. EXAMPLES:: sage: is_pseudo_partition([5, 3, 1]) True sage: is_pseudo_partition([3, 2, 1]) True sage: is_pseudo_partition([2, 2, 2]) True sage: is_pseudo_partition([1, 2, 3]) True sage: is_pseudo_partition([1, 3, 5]) False .. SEEALSO:: :meth:`staircase_shape` """ # TODO: test if not all(term in IntegerRing() for term in seq): return False for index in range(len(seq) - 1): if seq[index] + 1 < seq[index + 1]: return False return True
def _generate_path(next_func, start): r""" Return the list generated by ``start`` by applying ``next_func`` to it repeatedly until reading ``None``. A helper function. EXAMPLES:: sage: def fun(x): ....: if x == 4: ....: return None ....: else: ....: return x + 1 ....: sage: _generate_path(fun, -2) [-2, -1, 0, 1, 2, 3] """ path = [start] while True: next_ = next_func(path[-1]) if next_ is not None: path.append(next_) else: break return path
[docs]def staircase_shape(n): r""" Given ``n``, return the composition `[n-1, n-2, \ldots, 0]` commonly denoted `\rho`. Yes, this INCLUDES a 0 at the end! EXAMPLES:: sage: staircase_shape(3) [2, 1, 0] .. SEEALSO:: :meth:`RootIdeals.init_staircase` """ return Composition(range(n - 1, -1, -1))
[docs]def skew_partition_to_selected_rows(sp): r""" Given a skew partition ``sp``, follow the bounce algorithm and return the indices of the selected rows. EXAMPLES:: sage: SkewPartition([[], []]).to_selected_rows() [] sage: SkewPartition([[1], []]).to_selected_rows() [0] sage: SkewPartition([[3, 2, 1], [1]]).to_selected_rows() [0, 1] sage: SkewPartition([[6, 5, 3, 2, 2, 1], [2, 2]]).to_selected_rows() [0, 1, 2, 5] .. SEEALSO:: :meth:`selected_rows_to_maximum_root_ideal` """ # actually this may ONLY WORK for catty-connected skew-partitions, because i'm not sure how we deal with 'missing' rows # arguably we should call it a linked_skew_partition # record the indices of rows that have been used up def bounce_path(sp, row_index, blocked_rows=set()): def bounce_path_piece(sp, start_row_index, blocked_rows=set()): # Helper # this algo find the correct "L" piece of the path, where the bottom right cell is cell1, the bottom left is cell2, and the top left is cell3 # Returns (top_row_index, is_end) which are the row index of cell3 and whether or not we 'broke free' out of the top left cell of the skew-partition, respectively. col_index2 = skew_partition.left(sp, start_row_index) row_index3 = skew_partition.top(sp, col_index2) + 1 while row_index3 in blocked_rows: row_index3 += 1 # CATTY-CORNER ONLY line: max_row_index = len(sp.outer()) - 1 if row_index3 > max_row_index: return None, True else: return row_index3, False # helper new_blocked_rows = {row_index} # is_end says if you reached the end of the path while True: row_index, is_end = bounce_path_piece(sp, row_index, blocked_rows) if is_end: break else: new_blocked_rows.add(row_index) return new_blocked_rows selected_rows = set() blocked_rows = set() for row_index, outer_row in enumerate(sp.outer()): if row_index not in blocked_rows: selected_rows.add(row_index) new_blocked_rows = bounce_path(sp, row_index, blocked_rows) blocked_rows.update(new_blocked_rows) return sorted(selected_rows)
[docs]def selected_rows_to_maximum_root_ideal(n, selected_indices): r""" Given the dimension of the square and the selected rows, output the root ideal. Given the dimension ``n`` of the square and the selected rows whose indices are ``selected_indices``, return the maximum root ideal. EXAMPLES:: sage: selected_rows_to_maximum_root_ideal(1, [0]) RootIdeal([]) sage: selected_rows_to_maximum_root_ideal(2, [0, 1]) RootIdeal([]) sage: selected_rows_to_maximum_root_ideal(2, [0]) RootIdeal([(0, 1)]) sage: selected_rows_to_maximum_root_ideal(3, [0, 2]) RootIdeal([(0, 1), (0, 2)]) sage: selected_rows_to_maximum_root_ideal(4, [0, 2]) RootIdeal([(0, 1), (0, 2), (0, 3), (1, 3)]) sage: selected_rows_to_maximum_root_ideal(5, [0, 1, 4]) RootIdeal([(0, 2), (0, 3), (0, 4), (1, 3), (1, 4)]) sage: selected_rows_to_maximum_root_ideal(5, [0, 1]) RootIdeal([(0, 2), (0, 3), (0, 4), (1, 3), (1, 4), (2, 4)]) .. SEEALSO:: :meth:`skew_partition_to_selected_rows` """ root_ideal_cells = [] selected_indices = set(selected_indices) permitted_col_indices = set(range(n)) - selected_indices for i in range(n): if i in selected_indices: if permitted_col_indices: smallest_unblocked_index = min(permitted_col_indices) root_ideal_cells += [(i, j) for j in range(smallest_unblocked_index, n)] permitted_col_indices.remove(smallest_unblocked_index) selected_indices.add(smallest_unblocked_index) return RootIdeal(root_ideal_cells)
[docs]def skew_partition_to_removable_roots(sp, type='max'): r""" Given a SkewPartition ``sp``, return the removable roots of the corresponding 'min' or 'max' root ideal. INPUTS: - ``sp`` -- a :class:`SkewPartition` OPTIONAL INPUTS: - ``type`` -- (default ``'max'``) the type of root ideal you want to use. ``'min'`` is the minimum root ideal (as far as containment goes) and ``'max'`` is the maximum root ideal. OUTPUT: A list of removable roots in order. TODO: find an example where min is different than max EXAMPLES:: sage: SkewPartition([[], []]).to_removable_roots(type='min') [] sage: SkewPartition([[], []]).to_removable_roots(type='max') [] sage: SkewPartition([[3, 2, 1], [1]]).to_removable_roots(type='min') [(0, 2)] sage: SkewPartition([[3, 2, 1], [1]]).to_removable_roots(type='max') [(0, 2)] sage: SkewPartition([[6, 5, 3, 2, 2, 1], [2, 2]]).to_removable_roots(type='min') [(0, 3), (1, 4)] sage: SkewPartition([[6, 5, 3, 2, 2, 1], [2, 2]]).to_removable_roots(type='max') [(0, 3), (1, 4)] """ def type_shift(x, type): if type == 'max': return x elif type == 'min': return x - 1 else: raise ValueError('Bad type.') assert skew_partition.is_linked(sp) mu = Partition(sp.column_lengths()) eta = sp.inner() rmvble_roots = [] for i in range(0, len(eta)): mu_index = type_shift(eta[i], type) rmvble_root = (i, mu[mu_index] + i) rmvble_roots.append(rmvble_root) return rmvble_roots
[docs]def removable_roots_to_partition(corners, n): r""" Given the removable roots that define a root ideal, return the partition corresponding to that root ideal. Given the ``corners`` (removable roots) that define a root ideal as well as the size ``n`` of the `n` x `n` root ideal grid, return the partition corresponding to that root ideal. EXAMPLES: Consider that `n = 6` and the removable roots are `(0, 3)` and `(1, 4)`. This function essentially draws a 6 x 6 grid, and then fills in the cells `(0, 3)` and `(1, 4)`. .. image:: _static/removable-roots-to-partition.JPG :width: 240px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] All cells to the north and east of the filled in ones also become shaded. This shades the root ideal [(0, 3), (0, 4), (0, 5), (1, 4), (1, 5)] which corresponds to the partition [3, 2], so the function will return ``Partition([3, 2])``:: sage: removable_roots_to_partition([(0, 3), (1, 4)], 6) [3, 2] .. SEEALSO:: :meth:`RootIdeals.init_from_removable_roots`, :meth:`to_partition` """ corners = sorted(corners) # r is the row index or the 'y' value # c is the col index of the 'x' value previous_r = -1 ptn = [] for r in range(0, n): if corners: current_r = corners[0][0] current_c = corners[0][1] if current_r == r: # see how many rows to fill num_rows = r - previous_r ptn += [n - current_c] * num_rows # delete corner since it has now been used previous_r = current_r corners = corners[1:] return Partition(ptn)
# RootIdeal stuff def _is_roots(obj): r""" Helper function. Dirty indicator of whether object ``obj`` consists of roots. Returns ``True`` if it is an iterable of pairs of natural numbers. EXAMPLES:: sage: _is_roots(((1, 4), (7, 2))) True sage: _is_roots(((-1, 4), (7, 2))) False sage: _is_roots([(1, 4), (7, 2)]) True sage: _is_roots([(0, 4), (7, 2)]) True sage: _is_roots(set([(1, 4), (7, 2)])) True sage: _is_roots(set([[1, 4], [7, 2]])) False """ try: iter(obj) except: return False if not all(isinstance(el, tuple) and len(el) == 2 and el[0] in NonNegativeIntegerSemiring() and el[1] in NonNegativeIntegerSemiring() for el in obj): return False return True
[docs]class RootIdeal(list): r""" A root ideal. Consider the k-1 staircase partition `[k-1, k-2, \ldots, 1]` positioned in the upper-right corner of a `k` x `k` grid. The cells in the grid are labeled with (row_index, col_index) 0-based coordinates. Now consider any right-justified subpartition of the staircase partition. This is a RootIdeal. However, it is expressed not as a partition but as a list of the cells it contains. See Definition 2.1 of [cat]_ for more. EXAMPLES: The partition `[3, 1]` in the 7 x 7 grid is the root ideal `[(0,4), (0,5), (0,6), (1,6)]`:: sage: ri = RootIdeal([(0,4), (0,5), (0,6), (1,6)]) """ def __init__(self, lis, n=None): # validate the roots assert _is_roots(lis) # figure out n n_best_guess = max(c for (r, c) in lis) + 1 if lis else 1 if n is not None: if n < n_best_guess: raise ValueError('There is a root that falls outside of the n x n grid. n is too small or one of the roots is incorrect.') self.n = n else: self.n = n_best_guess # normalize the roots lis = sorted(lis) list.__init__(self, lis) def __hash__(self): r""" Return the hash of this root ideal. Thanks to this method, every :class:`RootIdeal` object is hashable, which is needed internally for other things to work. EXAMPLES:: sage: ri = RootIdeal([(0,4), (0,5), (0,6), (1,6)]) sage: hash(ri) 6635400540396992191 """ return hash(tuple(self))
[docs] def next_within_bounds(self, min=[], max=None, type='strict'): r"""Get the next root ideal lexicographically that contains min and is contained in max. This is the same method as :meth:`Partition.next_within_bounds`, but using the corresponding root ideals instead of partitions. It is best to understand that method before looking at this one. INPUTS: - ``self`` -- The RootIdeal. - ``min`` -- (default ``[]``, the empty root ideal) The 'minimum root ideal' that ``next(self)`` must contain. - ``max`` -- (default ``None``) The 'maximum root ideal' that ``next(self)`` must be contained in. If set to ``None``, then there is no restriction. - ``type`` -- (default ``'strict'``) The type of root ideals allowed. For example, 'strict' for strictly decreasing root ideals, or ``None`` to allow any valid upper root ideal. Note also that the default type is ``'strict'``, whereas :meth:`Partition.next_within_bounds` has default type ``None``! EXAMPLES:: sage: m = [(0,3), (1,3)] sage: M = [(0,1), (0,2), (0,3), (1,1), (1,2), (1,3)] sage: RootIdeal([(0, 3), (1, 3)], n=4).next_within_bounds(min=m, max=M) [(0, 2), (0, 3), (1, 3)] sage: RootIdeal([(0, 2), (0, 3), (1, 3)], n=4).next_within_bounds(min=m, max=M) [(0, 1), (0, 2), (0, 3), (1, 3)] sage: RootIdeal([(0, 1), (0, 2), (0, 3), (1, 3)], n=4).next_within_bounds(min=m, max=M) [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)] sage: RootIdeal([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], n=4).next_within_bounds(min=m, max=M) == None True .. SEEALSO:: :meth:`Partition.next_within_bounds` """ if not (isinstance(min, RootIdeal) or _is_roots(min)): raise ValueError('Input parameter ``min`` must be a RootIdeal or an iterable of roots. Your inputted ``min`` parameter was: {}'.format(min)) if not (isinstance(max, RootIdeal) or _is_roots(max) or max is None): raise ValueError('Input parameter ``max`` must be a RootIdeal, an iterable of roots, or ``None``. Your inputted ``min`` parameter was: {}'.format(max)) n = self.n ptn = self.to_partition() min_ptn = RootIdeal(min, n=n).to_partition() max_ptn = RootIdeal(max, n=n).to_partition( ) if max is not None else None if type in ('strict', 'rational'): type = 'strictly decreasing' next_ptn = partition.next_within_bounds(ptn, min=min_ptn, max=max_ptn, type=type) next_ri = RootIdeals().init_from_partition(next_ptn, n) return next_ri
[docs] def down(ri, row_index): r""" Starting at the row corresponding to ``row_index``, return the down. Given a root ideal ``ri`` and a starting position ``row_index``, move right on that row until you hit the root ideal (you are now standing ontop of a cell of the root ideal), then move straight down until you hit the diagonal, and return the new index. EXAMPLES: The picture below represents the root ideal ``ri``: .. image:: _static/root-ideal.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.down(0) 2 sage: ri.down(1) 3 sage: ri.down(2) 4 sage: ri.down(3) == None True sage: ri.down(4) == None True .. SEEALSO:: :meth:`up`, :meth:`down_path`, :meth:`up_path`, :meth:`top`, :meth:`bottom` """ # Note: I am assuming the cells in the root ideal are IN ORDER with y coordinates weakly increasing, and for fixed y, x strictly increasing for (r, c) in ri: if r == row_index: return c return None
[docs] def up(root_ideal, col_index): r""" Starting at the column corresponding to ``col_index``, return the up. Same as :meth:`down`, but this time you start in the *column* indicated by ``column_index``, and move *up* until you hit the root ideal, then move *left* until you hit the diagonal. EXAMPLES: The picture below represents the root ideal ``ri``: .. image:: _static/root-ideal.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.up(0) == None True sage: ri.up(1) == None True sage: ri.up(2) 0 sage: ri.up(3) 1 sage: ri.up(4) 2 .. SEEALSO:: :meth:`down`, :meth:`down_path`, :meth:`up_path`, :meth:`top`, :meth:`bottom` """ for (r, c) in reversed(root_ideal): if c == index: return r return None
[docs] def down_path(root_ideal, start_index): r""" Given a starting row index ``start_index``, perform :meth:`down` operations repeatedly until you can't anymore. Returns the resulting sequence of indices as a list. (See [cat]_ Definition 5.2 for more) EXAMPLES: The picture below represents the root ideal used in the example, and the path drawn on the picture depicts the down path for ``start_index`` 0 specifically: .. image:: _static/bottom.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.down_path(0) [0, 2, 4] sage: ri.down_path(1) [1, 3] sage: ri.down_path(2) [2, 4] sage: ri.down_path(3) [3] sage: ri.down_path(4) [4] .. SEEALSO:: :meth:`down`, :meth:`up`, :meth:`up_path`, :meth:`top`, :meth:`bottom` """ def next_func(index): return root_ideal.down(index) return _generate_path(next_func, start_index)
[docs] def up_path(root_ideal, start_index): r""" Same as :meth:`down_path`, but uses a *column* index to start with, and applies *up* operations repeatedly. EXAMPLES: The picture below represents the root ideal ``ri``: .. image:: _static/root-ideal.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.up_path(0) [0] sage: ri.up_path(1) [1] sage: ri.up_path(2) [2, 0] sage: ri.up_path(3) [3, 1] sage: ri.up_path(4) [4, 2, 0] .. SEEALSO:: :meth:`down`, :meth:`up`, :meth:`down_path`, :meth:`top`, :meth:`bottom` """ def next_func(index): return root_ideal.up(index) return _generate_path(next_func, start_index)
[docs] def top(root_ideal, start_index): r""" Given a column index ``start_index``, look at it's :meth:`up_path` and return the final index. EXAMPLES: The picture below represents the root ideal ``ri``: .. image:: _static/root-ideal.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.top(0) 0 sage: ri.top(1) 1 sage: ri.top(2) 0 sage: ri.top(3) 1 sage: ri.top(4) 0 .. SEEALSO:: :meth:`down`, :meth:`up`, :meth:`down_path`, :meth:`up_path`, :meth:`bottom` """ return root_ideal.up_path(start_index)[-1]
[docs] def bottom(root_ideal, start_index): r""" Given a row index ``start_index``, look at it's :meth:`down_path` and return the final index. EXAMPLES: The picture below represents the root ideal used in the examples, and the path drawn on the picture depicts the down path for index 0 specifically, demonstrating that ``bottom(0)`` should be 4: .. image:: _static/bottom.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] :: sage: ri = RootIdeals().init_from_partition([3, 2, 1], 5) sage: ri.bottom(0) 4 sage: ri.bottom(1) 3 sage: ri.bottom(2) 4 sage: ri.bottom(3) 3 sage: ri.bottom(4) 4 .. SEEALSO:: :meth:`down`, :meth:`up`, :meth:`down_path`, :meth:`up_path`, :meth:`top` """ return root_ideal.down_path(start_index)[-1]
[docs] def down_path_column_lengths_part(root_ideal, ptn, start_index): r""" This is `\mu_i` in Definition 2.3 of [scat]_. This exists mainly as a helper function for :meth:`down_path_column_lengths`. EXAMPLES: sage: ri = RootIdeals().init_from_partition([5, 2, 2, 2], 6) sage: ptn = [7, 6, 5, 2, 2, 2] sage: ri.down_path_column_lengths_part(ptn, 0) 15 sage: ri.down_path_column_lengths_part(ptn, 1) 8 sage: ri.down_path_column_lengths_part(ptn, 3) 4 sage: ri.down_path_column_lengths_part(ptn, 2) 7 sage: ri.down_path_column_lengths_part(ptn, 4) 2 sage: ri.down_path_column_lengths_part(ptn, 5) 2 sage: ri.down_path_column_lengths_part(ptn, 6) Traceback (most recent call last): ... IndexError: list index out of range This is also the lengths of the bounce paths in [cat]_ Definition 5.2. """ return sum(ptn[j] for j in root_ideal.down_path(start_index))
[docs] def down_path_column_lengths(self, ptn): r""" This is the column shape `\mu'` as defined by Definition 2.3 of [scat]_. It is also introduced in the second paragraph of the overview as `\mathfrak{cs}(\Psi, \lambda)`. EXAMPLES: In Example 2.4 of [scat]_, the following .. image:: _static/example2.4.png :align: center :alt: The root ideal [(0,1), (0,2), (0,3), (0,4), (0,5), (1,4), (1,5), (2,4), (2,5), (3,4), (3,5)] and the partition 7 6 5 2 2 2 depicts the root ideal in red and the partition on the diagonal:: sage: ri = RootIdeals().init_from_partition([5, 2, 2, 2], 6) sage: ptn = [7, 6, 5, 2, 2, 2] sage: ri.down_path_column_lengths(ptn) [15, 7, 4, 2] This is also the lengths of the bounce paths in [cat]_ Definition 5.2. .. SEEALSO:: :meth:`down_path` """ if not self: mu = ptn else: mu = [] # n is the side length of the square n = self.n indices_available = set(range(0, n)) for index in range(0, n): if index in indices_available: # add the kthing to mu mu.append(self.down_path_column_lengths_part(ptn, index)) # remove indices from future draws dpath = self.down_path(index) indices_available -= set(dpath) return Partition(mu)
[docs] def to_partition(root_ideal): r""" Given a root ideal (list of cells), return the corresponding partition (the row shape of the root ideal). Returns a :class:`Partition` object. EXAMPLES: The red part of the following picture (please ignore the diagonal) represents the root ideal [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]. .. image:: _static/Ksi.png :align: center :alt: The root ideal [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] inside a 6 by 6 grid. But it can also be interpreted as the partition 5 2 2 2 (in the Hebrew convention). Therefore, ``to_partition()`` acting on the root ideal will output 5 2 2 2:: sage: ri = RootIdeal([(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]) sage: ri.to_partition() [5, 2, 2, 2] .. SEEALSO:: :meth:`RootIdeals.init_from_partition` """ if root_ideal is None or root_ideal == False: return root_ideal if not root_ideal: ptn = [] else: max_r = root_ideal[-1][0] ptn = [0] * (max_r + 1) for (r, c) in root_ideal: ptn[r] += 1 return Partition(ptn)
[docs] def is_strict(ri): r""" Return if this root ideal's corresponding partition is strictly decreasing. Given a root ideal ``ri``, check to see if it is a *strict root ideal*, as defined in Example 2.4 of [scat]_. This merely means that it's corresponding partition is strictly decreasing! EXAMPLES: In the following images, ignore the index on the diagonal and look only at the root ideal in red. .. image:: _static/Phi.png :align: center :alt: The root ideal corresponding to the partition 8 6 5 3 2 :: sage: ri = RootIdeals().init_from_partition([8, 6, 5, 3, 2], 9) sage: ri.is_strict() True .. image:: _static/Ksi.png :align: center :alt: The root ideal corresponding to the partition 5 2 2 2 :: sage: ri = RootIdeals().init_from_partition([5, 2, 2, 2], 6) sage: ri.is_strict() False .. SEEALSO:: :meth:`partition.is_strictly_decreasing` """ ptn = ri.to_partition() return partition.is_strictly_decreasing(ptn)
[docs] def complement(self): r""" Return this root ideal's complement in the upper-staircase-shape. Given a root ideal (or just an iterable of roots), return it's complement in the upper-staircase-shape, the result being a root ideal (or just an iterable of roots). INPUTS: - ``ri`` -- a root ideal OPTIONAL INPUTS: - ``n`` -- (default ``None``) the side length of the n x n box you want the complement to be taken over. EXAMPLES: The two root ideals depicted below are complements of each other: .. image:: _static/root-ideal.JPG :width: 180px :align: center :alt: The root ideal [(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)] .. image:: _static/root-ideal-complement.JPG :width: 180px :align: center :alt: The root ideal [(0,1), (1,2), (2,3), (3,4)] :: sage: ri1 = RootIdeal([(0,2), (0,3), (0,4), (1,3), (1,4), (2,4)]) sage: ri2 = RootIdeal([(0,1), (1,2), (2,3), (3,4)]) sage: ri1.complement() == ri2 True sage: ri2.complement() == ri1 True .. SEEALSO:: :meth:`RootIdeals.init_staircase` """ n = self.n ri_staircase = RootIdeals().init_staircase(n) ri_complement_set = set(ri_staircase) - set(self) ri_complement = RootIdeal(ri_complement_set, n) return ri_complement
[docs]class RootIdeals: r""" The family of root ideals. Use this class as a factory to initialize a :class:`RootIdeal` object with any valid identifying data. See the ``init_from...`` methods below for ways to create a root ideal. Remember that you can also create a root ideal directly from an iterable of roots using :class:`RootIdeal`. """
[docs] def init_from_removable_roots(self, corners, n): r""" Given the removable roots ``corners`` of a root ideal and the size length `n` of the `n` x `n` grid, return the root ideal itself. EXAMPLES: The root ideal [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] in the `6` x `6` grid shown below in red (please ignore the diagonal) has removable roots `(0, 1)` and `(3, 4)`: .. image:: _static/Ksi.png :align: center :alt: The root ideal corresponding to the partition 5 2 2 2 :: sage: removable_roots_to_root_ideal({(0, 1), (3, 4)}, 6) [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] """ ptn = removable_roots_to_partition(corners, n) ri = self.init_from_partition(ptn, n) return ri
[docs] def init_from_skew_partition(self, sp, type='max', algorithm='removable roots'): r""" Given a :class:`SkewPartition` ``sp`` and a type of root ideal ('max' or 'min'), return the corresponding root ideal. A type of ``'min'`` returns `\Phi(\lambda, \mu)` while a type of ``'max'`` returns `\Phi^+(\lambda, \mu)` as notated in [scat]_ at the bottom of page 1. EXAMPLES:: sage: RootIdeals().init_from_skew_partition(SkewPartition([[4, 2, 1, 1], [2, 1]]), type='min') [(0, 1), (0, 2), (0, 3), (1, 3)] sage: RootIdeals().init_from_skew_partition(SkewPartition([[4, 2, 1, 1], [2, 1]]), type='max') [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)] sage: sp = SkewPartition([[6, 5, 3, 2, 2, 1], [2, 2]]) sage: RootIdeals().init_from_skew_partition(sp) [(0, 3), (0, 4), (0, 5), (1, 4), (1, 5)] .. SEEALSO:: :meth:`init_all_from_skew_partition` """ if algorithm == 'removable roots': corners = skew_partition_to_removable_roots(sp, type) n = len(sp.outer()) root_ideal = self.init_from_removable_roots(corners, n) elif algorithm == 'bounce': if type != 'max': raise Exception( 'The bounce algorithm can only yield the maximum root ideal (type=max).') selected_indices = skew_partition_to_selected_rows(sp) n = len(sp.outer()) root_ideal = selected_rows_to_maximum_root_ideal( n, selected_indices) else: raise ValueError('The requested algorithm "{}" does not exist.'.format(algorithm)) return RootIdeal(root_ideal)
[docs] def init_all_from_skew_partition(self, sp, type='strict'): r""" Given a :class:`SkewPartition` ``sp``, find the corresponding set (but given as a list here) of root ideals. (This is the set `\{\Psi \in \Delta^+(\mathfrak{R}) \mid \Phi(\lambda, \mu) \subset \Psi \subset \Phi^+(\lambda, \mu)\} = [(\lambda, \mu)]` found in [scat]_ at the bottom of page 1.) EXAMPLES:: sage: sp = SkewPartition([[4, 2, 1, 1], []]) sage: RootIdeals().init_all_from_skew_partition(sp) [[(0, 1), (0, 2), (0, 3), (1, 3)], [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)]] .. SEEALSO:: :meth:`init_from_skew_partition` """ # We could change this to an iterator if users may not want all the root ideals. min_ri = self.init_from_skew_partition(sp, type='min') max_ri = self.init_from_skew_partition(sp, type='max') n = len(sp.outer()) def next_func(ri): return ri.next_within_bounds(min=min_ri, max=max_ri, type=type) ris = _generate_path(next_func, min_ri) return ris
[docs] def init_k_schur_from_pseudo_partition(self, seq, k, n=None): r""" Given a ``k``-bounded "pseudo-partition" ``seq`` `= \mu` and the dimension `n` of the `n` x `n` grid, return the corresponding `k`-Schur root ideal `\Delta^k(\mu)`, as defined in [cat]_ Definition 2.2 as .. math:: \Delta^k(\mu) = \{(i,j) \in \Delta^+_n \mid k - \mu_i + i < j \} EXAMPLES: The following diagram depicts the `k`-bounded partition on the diagonal and the resulting `k`-schur root ideal: .. image:: _static/k-schur-root-ideal.JPG :height: 200px :align: center :alt: The partition 3 3 2 2 2 and the root ideal [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (2, 5), (2, 6), (2, 7), (3, 6), (3, 7), (4, 7)] :: sage: k_ri = RootIdeals().init_k_schur_from_partition([3, 3, 2, 2, 2], 4, n=8) sage: k_ri.to_partition() [6, 5, 3, 2, 1] .. SEEALSO:: :meth:`is_pseudo_partition` """ assert is_pseudo_partition(seq) assert all(term <= k for term in seq) if n is None: n = len(seq) else: assert len(seq) <= n ri = [] for i, part in enumerate(seq): ri += [(i, j) for j in range(k - part + i + 1, n)] return RootIdeal(ri)
[docs] def init_from_partition(self, ptn, n): r""" Given a partition and the size of the square, return the corresponding root ideal. (This is the inverse function to :meth:`RootIdeal.to_partition` in the context of an `n` x `n` grid.) EXAMPLES: The red part of the following picture (please ignore the diagonal) can be interpreted as the partition 5 2 2 2 (in the Hebrew convention): .. image:: _static/Ksi.png :align: center :alt: The root ideal [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] inside a 6 by 6 grid. Therefore the partition 5 2 2 2 with `n=6` corresponds to the root ideal [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]:: sage: RootIdeals().init_from_partition([5, 2, 2, 2], 6) [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] """ if ptn is None or ptn == False: return ptn ptn = Partition(ptn) root_ideal = [] for r, part in enumerate(ptn): root_ideal += [(r, c) for c in range(n-part, n)] return RootIdeal(root_ideal)
[docs] def init_staircase(self, n): r""" Given `n`, return the root ideal commonly denoted `\Delta^+`, which is the maximum possible root ideal in an `n` x `n` grid. EXAMPLES:: sage: RootIdeals().init_staircase(3) [(0,1), (0,2), (1,2)] sage: RootIdeals().init_staircase(4) [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)] .. SEEALSO:: :meth:`staircase_shape` """ return self.init_from_partition(staircase_shape(n), n)
[docs] def init_parabolic_from_composition(self, composition): r""" Given a composition of positive integers, return the parabolic root ideal. Given a composition `\eta` of positive integers, return the parabolic root ideal `\Delta(\eta)` defined by .. math:: \Delta(\eta) := \{ \alpha \in \Delta^+_{|\eta|} \:\text{above the block diagonal with block sizes}\: \eta_1, \ldots, \eta_r\} in [cat]_ just below Conjecture 3.3. EXAMPLES: .. image:: _static/parabolic-root-ideal.png :width: 200px :align: center :alt: The root ideal [(0,1), (0,2), (0,3), (0,4), (0,5), (1,4), (1,5), (2,4), (2,5), (3,4), (3,5)] :: sage: RootIdeals().init_parabolic_from_composition([1, 3, 2]) [(0,1), (0,2), (0,3), (0,4), (0,5), (1,4), (1,5), (2,4), (2,5), (3,4), (3,5)] """ assert partition._is_sequence(composition) assert all(term >= 1 for term in composition) if isinstance(composition, (Partition, Composition)): composition = list(composition) cells_complement = [] index_previous = -1 for term in composition: indices = range(index_previous + 1, index_previous + 1 + term) for index in indices: cells = [(index, j) for j in range(index + 1, indices[-1] + 1)] cells_complement += cells index_previous = indices[-1] n = sum(composition) complement_ri = RootIdeal(cells_complement, n) ri = complement_ri.complement() return ri