Brent Yorgey’s fast algorithm for integer vector (multiset) partitions - Combinatorics

Skip to content
Back to top
View this page
Edit this page

# Brent Yorgey’s fast algorithm for integer vector (multiset) partitions

ALGORITHM:

Brent Yorgey, Generating Multiset Partitions, The Monad Reader, Issue 8, September 2007, p. 5.

https://wiki.haskell.org/The_Monad.Reader/Previous_issues

AUTHORS:

- D. K. Sunko (2020-02-19): initial version

- F. Chapoton (2020-02-22): conversion to iterators and shorter doctests and doc tweaks

- T. Scrimshaw (2020-03-06): Cython optimizations and doc tweaks

sage.combinat.fast_vector_partitions.fast_vector_partitions(v, min_vals=None)

  Brent Yorgey’s fast algorithm for integer vector (multiset) partitions.

  INPUT:

  - `v` – list of nonnegative integers, understood as the vector to be partitioned

  - `min_vals` – (optional) list of nonnegative integers, of same length as `v`

  OUTPUT: list of lists, each representing a vector partition of `v`

  If `min_vals` is given, only partitions with parts `p >= min_vals` in the lexicographic ordering will appear.

  If `min_vals` is given and `len(min_vals) != len(v)`, an error is raised.

  EXAMPLES:

  The older the computer, the more impressive the comparison:

  Sage

      sage: from sage.combinat.fast_vector_partitions import fast_vector_partitions
      sage: fastvparts = list(fast_vector_partitions([3, 3, 3]))
      sage: vparts = list(VectorPartitions([3, 3, 3]))
      sage: vparts == fastvparts[::-1]
      True
      sage: len(fastvparts)
      686
      sage: list(fast_vector_partitions([1, 2, 3], min_vals=[0, 1, 1]))
      [[[1, 2, 3]],
       [[0, 2, 3], [1, 0, 0]],
       [[0, 2, 2], [1, 0, 1]],
       [[0, 2, 1], [1, 0, 2]],
       [[0, 2, 0], [1, 0, 3]],
       [[0, 1, 3], [1, 1, 0]],
       [[0, 1, 2], [1, 1, 1]],
       [[0, 1, 1], [1, 1, 2]],
       [[0, 1, 1], [0, 1, 2], [1, 0, 0]],
       [[0, 1, 1], [0, 1, 1], [1, 0, 1]]]
      sage: L1 = list(fast_vector_partitions([5, 7, 6], min_vals=[1, 3, 2]))
      sage: L1 == list(VectorPartitions([5, 7, 6], min=[1, 3, 2]))[::-1]
      True

  Python

      >>> from sage.all import *
      >>> from sage.combinat.fast_vector_partitions import fast_vector_partitions
      >>> fastvparts = list(fast_vector_partitions([Integer(3), Integer(3), Integer(3)]))
      >>> vparts = list(VectorPartitions([Integer(3), Integer(3), Integer(3)]))
      >>> vparts == fastvparts[::-Integer(1)]
      True
      >>> len(fastvparts)
      686
      >>> list(fast_vector_partitions([Integer(1), Integer(2), Integer(3)], min_vals=[Integer(0), Integer(1), Integer(1)]))
      [[[1, 2, 3]],
       [[0, 2, 3], [1, 0, 0]],
       [[0, 2, 2], [1, 0, 1]],
       [[0, 2, 1], [1, 0, 2]],
       [[0, 2, 0], [1, 0, 3]],
       [[0, 1, 3], [1, 1, 0]],
       [[0, 1, 2], [1, 1, 1]],
       [[0, 1, 1], [1, 1, 2]],
       [[0, 1, 1], [0, 1, 2], [1, 0, 0]],
       [[0, 1, 1], [0, 1, 1], [1, 0, 1]]]
      >>> L1 = list(fast_vector_partitions([Integer(5), Integer(7), Integer(6)], min_vals=[Integer(1), Integer(3), Integer(2)]))
      >>> L1 == list(VectorPartitions([Integer(5), Integer(7), Integer(6)], min=[Integer(1), Integer(3), Integer(2)]))[::-Integer(1)]
      True

  Note

  The partitions are returned as an iterator.

  In this documentation, `a <|= b` means `a[i] <= b[i]` for all `i` (notation following B. Yorgey’s paper). It is the monomial partial ordering in Dickson’s lemma: `a <|= b` iff `x^a` divides `x^b` as monomials.

  Warning

  The ordering of the partitions is reversed with respect to the output of Sage class `VectorPartitions`.

sage.combinat.fast_vector_partitions.recursive_vector_partitions(v, vL)

  Iterate over a lexicographically ordered list of lists, each list representing a vector partition of `v`, such that no part of any partition is lexicographically smaller than `vL`.

  Internal part of `fast_vector_partitions()`.

  INPUT:

  - `v` – list of nonnegative integers, understood as a vector

  - `vL` – list of nonnegative integers, understood as a vector

  EXAMPLES:

  Sage

      sage: from sage.combinat.fast_vector_partitions import recursive_vector_partitions
      sage: list(recursive_vector_partitions([2, 2, 2],[1, 1, 1]))
      [[[2, 2, 2]], [[1, 1, 1], [1, 1, 1]]]
      sage: list(recursive_vector_partitions([2, 2, 2],[1, 1, 0]))
      [[[2, 2, 2]], [[1, 1, 1], [1, 1, 1]], [[1, 1, 0], [1, 1, 2]]]
      sage: list(recursive_vector_partitions([2, 2, 2],[1, 0, 1]))
      [[[2, 2, 2]],
       [[1, 1, 1], [1, 1, 1]],
       [[1, 1, 0], [1, 1, 2]],
       [[1, 0, 2], [1, 2, 0]],
       [[1, 0, 1], [1, 2, 1]]]

  Python

      >>> from sage.all import *
      >>> from sage.combinat.fast_vector_partitions import recursive_vector_partitions
      >>> list(recursive_vector_partitions([Integer(2), Integer(2), Integer(2)],[Integer(1), Integer(1), Integer(1)]))
      [[[2, 2, 2]], [[1, 1, 1], [1, 1, 1]]]
      >>> list(recursive_vector_partitions([Integer(2), Integer(2), Integer(2)],[Integer(1), Integer(1), Integer(0)]))
      [[[2, 2, 2]], [[1, 1, 1], [1, 1, 1]], [[1, 1, 0], [1, 1, 2]]]
      >>> list(recursive_vector_partitions([Integer(2), Integer(2), Integer(2)],[Integer(1), Integer(0), Integer(1)]))
      [[[2, 2, 2]],
       [[1, 1, 1], [1, 1, 1]],
       [[1, 1, 0], [1, 1, 2]],
       [[1, 0, 2], [1, 2, 0]],
       [[1, 0, 1], [1, 2, 1]]]

sage.combinat.fast_vector_partitions.recursive_within_from_to(m, s, e, useS, useE)

  Iterate over a lexicographically ordered list of lists `v` satisfying `e <= v <= s` and `v <|= m` as vectors.

  Internal part of `fast_vector_partitions()`.

  INPUT:

  - `m` – list of nonnegative integers, understood as a vector

  - `s` – list of nonnegative integers, understood as a vector

  - `e` – list of nonnegative integers, understood as a vector

  - `useS` – boolean

  - `useE` – boolean

  EXAMPLES:

  Sage

      sage: from sage.combinat.fast_vector_partitions import recursive_within_from_to
      sage: list(recursive_within_from_to([1, 2, 3],[1, 2, 2],[1, 1, 1],True,True))
      [[1, 2, 2], [1, 2, 1], [1, 2, 0], [1, 1, 3], [1, 1, 2], [1, 1, 1]]

  Python

      >>> from sage.all import *
      >>> from sage.combinat.fast_vector_partitions import recursive_within_from_to
      >>> list(recursive_within_from_to([Integer(1), Integer(2), Integer(3)],[Integer(1), Integer(2), Integer(2)],[Integer(1), Integer(1), Integer(1)],True,True))
      [[1, 2, 2], [1, 2, 1], [1, 2, 0], [1, 1, 3], [1, 1, 2], [1, 1, 1]]

  Note

  The flags `useS` and `useE` are used to implement the condition efficiently. Because testing it loops over the vector, re-testing at each step as the vector is parsed is inefficient: all but the last comparison have been done cumulatively already. This code tests only for the last one, using the flags to accumulate information from previous calls.

  Warning

  Expects to be called with `s <|= m`.

  Expects to be called first with `useS == useE == True`.

sage.combinat.fast_vector_partitions.within_from_to(m, s, e)

  Iterate over a lexicographically ordered list of lists `v` satisfying `e <= v <= s` and `v <|= m` as vectors.

  Internal part of `fast_vector_partitions()`.

  INPUT:

  - `m` – list of nonnegative integers, understood as a vector

  - `s` – list of nonnegative integers, understood as a vector

  - `e` – list of nonnegative integers, understood as a vector

  EXAMPLES:

  Sage

      sage: from sage.combinat.fast_vector_partitions import within_from_to
      sage: list(within_from_to([1, 2, 3], [1, 2, 2], [1, 1, 1]))
      [[1, 2, 2], [1, 2, 1], [1, 2, 0], [1, 1, 3], [1, 1, 2], [1, 1, 1]]

  Python

      >>> from sage.all import *
      >>> from sage.combinat.fast_vector_partitions import within_from_to
      >>> list(within_from_to([Integer(1), Integer(2), Integer(3)], [Integer(1), Integer(2), Integer(2)], [Integer(1), Integer(1), Integer(1)]))
      [[1, 2, 2], [1, 2, 1], [1, 2, 0], [1, 1, 3], [1, 1, 2], [1, 1, 1]]

  Note

  The input `s` will be “clipped” internally if it does not satisfy the condition `s <|= m`.

  To understand the input check, some line art is helpful. Assume that `(a,b)` are the two least significant coordinates of some vector. Say:

      e = (2,3), s = (7,6), m = (9,8).

  In the figure, these values are denoted by `E`, `S`, and `M`, while the letter `X` stands for all other allowed values of `v = (a,b)`:

      b ^
        |
      8 --------X---X---X---X---X-----------M
        |                                   |
      7 -       X   X   X   X   X           |
        |                                   |
      6 -       X   X   X   X   X   S       |
        |                                   |
      5 -       X   X   X   X   X   X       |
        |                                   |
      4 -       X   X   X   X   X   X       |
        |                                   |
      3 -       E   X   X   X   X   X       |
        |                                   |
      2 -           X   X   X   X   X       |
        |                                   |
      1 -           X   X   X   X   X       |
        |                                   |
      0 ----|---|---X---X---X---X---X---|---|--->
        0   1   2   3   4   5   6   7   8   9   a

  If `S` moves horizontally, the full-height columns fill the box in until `S` reaches `M`, at which point it remains the limit in the b-direction as it moves out of the box, while M takes over as the limit in the a-direction, so the `M`-column remains filled only up to `S`, no matter how much `S` moves further to the right.

  If `S` moves vertically, its column will be filled to the top of the box, but it remains the relevant limit in the a-direction, while `M` takes over in the b-direction as `S` goes out of the box upwards.

  Both behaviors are captured by using the smaller coordinate of `S` and `M`, whenever `S` is outside the box defined by M. The input will be “clipped” accordingly in that case.

  Warning

  The “clipping” behavior is transparent to the user, but may be puzzling when comparing outputs with the function `recursive_within_from_to()` which has no input protection.