Enumerated set of lists of integers with constraints: base classes - Combinatorics

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

# Enumerated set of lists of integers with constraints: base classes

- `IntegerListsBackend`: base class for the Cython back-end of an enumerated set of lists of integers with specified constraints.

- `Envelope`: a utility class for upper (lower) envelope of a function under constraints.

class sage.combinat.integer_lists.base.Envelope

  Bases: `object`

  The (currently approximated) upper (lower) envelope of a function under the specified constraints.

  INPUT:

  - `f` – a function, list, or tuple; if `f` is a list, it is considered as the function `f(i)=f[i]`, completed for larger \(i\) with `f(i)=max_part`.

  - `min_part`, `max_part`, `min_slope`, `max_slope`, … as for `IntegerListsLex` (please consult for details).

  - `sign` – (+1 or -1) multiply the input values with `sign` and multiply the output with `sign`. Setting this to \(-1\) can be used to implement a lower envelope.

  The upper envelope \(U(f)\) of \(f\) is the (pointwise) largest function which is bounded above by \(f\) and satisfies the `max_part` and `max_slope` conditions. Furthermore, for `i,i+1<min_length`, the upper envelope also satisfies the `min_slope` condition.

  Upon computing \(U(f)(i)\), all the previous values for \(j\leq i\) are computed and cached; in particular \(f(i)\) will be computed at most once for each \(i\).

  Todo

  - This class is a good candidate for Cythonization, especially to get the critical path in `__call__` super fast.

  - To get full envelopes, we would want both the `min_slope` and `max_slope` conditions to always be satisfied. This is only properly defined for the restriction of \(f\) to a finite interval \(0,..,k\), and depends on \(k\).

  - This is the core “data structure” of `IntegerListsLex`. Improving the lookahead there essentially depends on having functions with a good complexity to compute the area below an envelope; and in particular how it evolves when increasing the length.

  EXAMPLES:

  Sage

      sage: from sage.combinat.integer_lists import Envelope

  Python

      >>> from sage.all import *
      >>> from sage.combinat.integer_lists import Envelope

  Trivial upper and lower envelopes:

  Sage

      sage: f = Envelope([3,2,2])
      sage: [f(i) for i in range(10)]
      [3, 2, 2, inf, inf, inf, inf, inf, inf, inf]
      sage: f = Envelope([3,2,2], sign=-1)
      sage: [f(i) for i in range(10)]
      [3, 2, 2, 0, 0, 0, 0, 0, 0, 0]

  [Python]

      >>> from sage.all import *
      >>> f = Envelope([Integer(3),Integer(2),Integer(2)])
      >>> [f(i) for i in range(Integer(10))]
      [3, 2, 2, inf, inf, inf, inf, inf, inf, inf]
      >>> f = Envelope([Integer(3),Integer(2),Integer(2)], sign=-Integer(1))
      >>> [f(i) for i in range(Integer(10))]
      [3, 2, 2, 0, 0, 0, 0, 0, 0, 0]

  A more interesting lower envelope:

  Sage

      sage: f = Envelope([4,1,5,3,5], sign=-1, min_part=2, min_slope=-1)
      sage: [f(i) for i in range(10)]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

  Python

      >>> from sage.all import *
      >>> f = Envelope([Integer(4),Integer(1),Integer(5),Integer(3),Integer(5)], sign=-Integer(1), min_part=Integer(2), min_slope=-Integer(1))
      >>> [f(i) for i in range(Integer(10))]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

  Currently, adding `max_slope` has no effect:

  Sage

      sage: f = Envelope([4,1,5,3,5], sign=-1, min_part=2, min_slope=-1, max_slope=0)
      sage: [f(i) for i in range(10)]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

  [Python]

      >>> from sage.all import *
      >>> f = Envelope([Integer(4),Integer(1),Integer(5),Integer(3),Integer(5)], sign=-Integer(1), min_part=Integer(2), min_slope=-Integer(1), max_slope=Integer(0))
      >>> [f(i) for i in range(Integer(10))]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

  unless `min_length` is large enough:

  Sage

      sage: f = Envelope([4,1,5,3,5], sign=-1, min_part=2, min_slope=-1, max_slope=0, min_length=2)
      sage: [f(i) for i in range(10)]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

      sage: f = Envelope([4,1,5,3,5], sign=-1, min_part=2, min_slope=-1, max_slope=0, min_length=4)
      sage: [f(i) for i in range(10)]
      [5, 5, 5, 4, 5, 4, 3, 2, 2, 2]

      sage: f = Envelope([4,1,5,3,5], sign=-1, min_part=2, min_slope=-1, max_slope=0, min_length=5)
      sage: [f(i) for i in range(10)]
      [5, 5, 5, 5, 5, 4, 3, 2, 2, 2]

  Python

      >>> from sage.all import *
      >>> f = Envelope([Integer(4),Integer(1),Integer(5),Integer(3),Integer(5)], sign=-Integer(1), min_part=Integer(2), min_slope=-Integer(1), max_slope=Integer(0), min_length=Integer(2))
      >>> [f(i) for i in range(Integer(10))]
      [4, 3, 5, 4, 5, 4, 3, 2, 2, 2]

      >>> f = Envelope([Integer(4),Integer(1),Integer(5),Integer(3),Integer(5)], sign=-Integer(1), min_part=Integer(2), min_slope=-Integer(1), max_slope=Integer(0), min_length=Integer(4))
      >>> [f(i) for i in range(Integer(10))]
      [5, 5, 5, 4, 5, 4, 3, 2, 2, 2]

      >>> f = Envelope([Integer(4),Integer(1),Integer(5),Integer(3),Integer(5)], sign=-Integer(1), min_part=Integer(2), min_slope=-Integer(1), max_slope=Integer(0), min_length=Integer(5))
      >>> [f(i) for i in range(Integer(10))]
      [5, 5, 5, 5, 5, 4, 3, 2, 2, 2]

  A non trivial upper envelope:

  Sage

      sage: f = Envelope([9,1,5,4], max_part=7, max_slope=2)
      sage: [f(i) for i in range(10)]
      [7, 1, 3, 4, 6, 7, 7, 7, 7, 7]

  [Python]

      >>> from sage.all import *
      >>> f = Envelope([Integer(9),Integer(1),Integer(5),Integer(4)], max_part=Integer(7), max_slope=Integer(2))
      >>> [f(i) for i in range(Integer(10))]
      [7, 1, 3, 4, 6, 7, 7, 7, 7, 7]

  adapt(m, j)

    Return this envelope adapted to an additional local constraint.

    INPUT:

    - `m` – nonnegative integer (starting value)

    - `j` – nonnegative integer (position)

    This method adapts this envelope to the additional local constraint imposed by having a part \(m\) at position \(j\). Namely, this returns a function which computes, for any \(i>j\), the minimum of the ceiling function and the value restriction given by the slope conditions.

    EXAMPLES:

    Sage

        sage: from sage.combinat.integer_lists import Envelope
        sage: f = Envelope(3)
        sage: g = f.adapt(1,1)
        sage: g is f
        True
        sage: [g(i) for i in range(10)]
        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

        sage: f = Envelope(3, max_slope=1)
        sage: g = f.adapt(1,1)
        sage: [g(i) for i in range(10)]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

    Python

        >>> from sage.all import *
        >>> from sage.combinat.integer_lists import Envelope
        >>> f = Envelope(Integer(3))
        >>> g = f.adapt(Integer(1),Integer(1))
        >>> g is f
        True
        >>> [g(i) for i in range(Integer(10))]
        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

        >>> f = Envelope(Integer(3), max_slope=Integer(1))
        >>> g = f.adapt(Integer(1),Integer(1))
        >>> [g(i) for i in range(Integer(10))]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

    Note that, in both cases above, the adapted envelope is only guaranteed to be valid for \(i>j\)! This is to leave potential room in the future for sharing similar adapted envelopes:

    Sage

        sage: g = f.adapt(0,0)
        sage: [g(i) for i in range(10)]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

        sage: g = f.adapt(2,2)
        sage: [g(i) for i in range(10)]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

        sage: g = f.adapt(3,3)
        sage: [g(i) for i in range(10)]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

    [Python]

        >>> from sage.all import *
        >>> g = f.adapt(Integer(0),Integer(0))
        >>> [g(i) for i in range(Integer(10))]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

        >>> g = f.adapt(Integer(2),Integer(2))
        >>> [g(i) for i in range(Integer(10))]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

        >>> g = f.adapt(Integer(3),Integer(3))
        >>> [g(i) for i in range(Integer(10))]
        [0, 1, 2, 3, 3, 3, 3, 3, 3, 3]

    Now with a lower envelope:

    Sage

        sage: f = Envelope(1, sign=-1, min_slope=-1)
        sage: g = f.adapt(2,2)
        sage: [g(i) for i in range(10)]
        [4, 3, 2, 1, 1, 1, 1, 1, 1, 1]
        sage: g = f.adapt(1,3)
        sage: [g(i) for i in range(10)]
        [4, 3, 2, 1, 1, 1, 1, 1, 1, 1]

    Python

        >>> from sage.all import *
        >>> f = Envelope(Integer(1), sign=-Integer(1), min_slope=-Integer(1))
        >>> g = f.adapt(Integer(2),Integer(2))
        >>> [g(i) for i in range(Integer(10))]
        [4, 3, 2, 1, 1, 1, 1, 1, 1, 1]
        >>> g = f.adapt(Integer(1),Integer(3))
        >>> [g(i) for i in range(Integer(10))]
        [4, 3, 2, 1, 1, 1, 1, 1, 1, 1]

  limit()

    Return a bound on the limit of `self`.

    OUTPUT: nonnegative integer or \(\infty\)

    This returns some upper bound for the accumulation points of this upper envelope. For a lower envelope, a lower bound is returned instead.

    In particular this gives a bound for the value of `self` at \(i\) for \(i\) large enough. Special case: for a lower envelop, and when the limit is \(\infty\), the envelope is guaranteed to tend to \(\infty\) instead.

    When `s=self.limit_start()` is finite, this bound is guaranteed to be valid for \(i>=s\).

    Sometimes it’s better to have a loose bound that starts early; sometimes the converse holds. At this point which specific bound and starting point is returned is not set in stone, in order to leave room for later optimizations.

    EXAMPLES:

    Sage

        sage: from sage.combinat.integer_lists import Envelope
        sage: Envelope([4,1,5]).limit()
        inf
        sage: Envelope([4,1,5], max_part=2).limit()
        2
        sage: Envelope([4,1,5], max_slope=0).limit()
        1
        sage: Envelope(lambda x: 3, max_part=2).limit()
        2

    Python

        >>> from sage.all import *
        >>> from sage.combinat.integer_lists import Envelope
        >>> Envelope([Integer(4),Integer(1),Integer(5)]).limit()
        inf
        >>> Envelope([Integer(4),Integer(1),Integer(5)], max_part=Integer(2)).limit()
        2
        >>> Envelope([Integer(4),Integer(1),Integer(5)], max_slope=Integer(0)).limit()
        1
        >>> Envelope(lambda x: Integer(3), max_part=Integer(2)).limit()
        2

    Lower envelopes:

    Sage

        sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit()
        2
        sage: Envelope([4,1,5], min_slope=0, sign=-1).limit()
        5
        sage: Envelope([4,1,5], sign=-1).limit()
        0

    [Python]

        >>> from sage.all import *
        >>> Envelope(lambda x: Integer(3), min_part=Integer(2), sign=-Integer(1)).limit()
        2
        >>> Envelope([Integer(4),Integer(1),Integer(5)], min_slope=Integer(0), sign=-Integer(1)).limit()
        5
        >>> Envelope([Integer(4),Integer(1),Integer(5)], sign=-Integer(1)).limit()
        0

    See also

    `limit_start()`

  limit_start()

    Return from which \(i\) on the bound returned by `limit` holds.

    See also

    `limit()` for the precise specifications.

    EXAMPLES:

    Sage

        sage: from sage.combinat.integer_lists import Envelope
        sage: Envelope([4,1,5]).limit_start()
        3
        sage: Envelope([4,1,5], sign=-1).limit_start()
        3

        sage: Envelope([4,1,5], max_part=2).limit_start()
        3

        sage: Envelope(4).limit_start()
        0
        sage: Envelope(4, sign=-1).limit_start()
        0

        sage: Envelope(lambda x: 3).limit_start() == Infinity
        True
        sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity
        True

        sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity
        True

    Python

        >>> from sage.all import *
        >>> from sage.combinat.integer_lists import Envelope
        >>> Envelope([Integer(4),Integer(1),Integer(5)]).limit_start()
        3
        >>> Envelope([Integer(4),Integer(1),Integer(5)], sign=-Integer(1)).limit_start()
        3

        >>> Envelope([Integer(4),Integer(1),Integer(5)], max_part=Integer(2)).limit_start()
        3

        >>> Envelope(Integer(4)).limit_start()
        0
        >>> Envelope(Integer(4), sign=-Integer(1)).limit_start()
        0

        >>> Envelope(lambda x: Integer(3)).limit_start() == Infinity
        True
        >>> Envelope(lambda x: Integer(3), max_part=Integer(2)).limit_start() == Infinity
        True

        >>> Envelope(lambda x: Integer(3), sign=-Integer(1), min_part=Integer(2)).limit_start() == Infinity
        True

  max_part
  max_slope
  min_slope
  sign

class sage.combinat.integer_lists.base.IntegerListsBackend

  Bases: `object`

  Base class for the Cython back-end of an enumerated set of lists of integers with specified constraints.

  This base implements the basic operations, including checking for containment using `_contains()`, but not iteration. For iteration, subclass this class and implement an `_iter()` method.

  EXAMPLES:

  Sage

      sage: from sage.combinat.integer_lists.base import IntegerListsBackend
      sage: L = IntegerListsBackend(6, max_slope=-1)
      sage: L._contains([3,2,1])
      True

  Python

      >>> from sage.all import *
      >>> from sage.combinat.integer_lists.base import IntegerListsBackend
      >>> L = IntegerListsBackend(Integer(6), max_slope=-Integer(1))
      >>> L._contains([Integer(3),Integer(2),Integer(1)])
      True

  ceiling
  floor
  max_length
  max_part
  max_slope
  max_sum
  min_length
  min_part
  min_slope
  min_sum