Skip to content

Commit

Permalink
Trac #18290: enhanced sets and cartesian products
Browse files Browse the repository at this point in the history
We implement several missing features of sets, enumerated sets and their
cartesian products.

We add methods `is_empty` and `is_finite` to all sets.

We implement most of the methods for cartesian products of sets and
enumerated sets (cardinality, rank/unrank, iteration).

For example, all commands below were hanging
{{{
sage: C = cartesian_product([Permutations(7), Permutations(9)])
sage: C.cardinality()
1828915200
sage: C.unrank(143872745)
([1, 5, 3, 6, 2, 4, 7], [5, 3, 2, 4, 7, 8, 9, 6, 1])
sage: C.rank(_)
143872745
sage: C.random_element()   # random
([4, 2, 6, 7, 1, 3, 5], [4, 7, 2, 8, 6, 3, 9, 5, 1])
}}}

URL: http://trac.sagemath.org/18290
Reported by: vdelecroix
Ticket author(s): Vincent Delecroix
Reviewer(s): Nicolas Thiéry
  • Loading branch information
Release Manager authored and vbraun committed May 13, 2015
2 parents 41d136b + 707bf1e commit 04d92fa
Show file tree
Hide file tree
Showing 10 changed files with 538 additions and 104 deletions.
31 changes: 31 additions & 0 deletions src/sage/categories/additive_magmas.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,37 @@ def zero_element(self):
deprecation(17694, ".zero_element() is deprecated. Use .zero() instead")
return self.zero()

def is_empty(self):
r"""
Return whether this set is empty.
Since this set is an additive magma it has a zero element and
hence is not empty. This method thus always returns ``False``.
EXAMPLES::
sage: A = AdditiveAbelianGroup([3,3])
sage: A in AdditiveMagmas()
True
sage: A.is_empty()
False
sage: B = CommutativeAdditiveMonoids().example()
sage: B.is_empty()
False
TESTS:
We check that the method `is_empty` is inherited from this
category in both examples above::
sage: A.is_empty.__module__
'sage.categories.additive_magmas'
sage: B.is_empty.__module__
'sage.categories.additive_magmas'
"""
return False

class ElementMethods:
# TODO: merge with the implementation in Element which currently
# overrides this one, and further requires self.parent()(0) to work.
Expand Down
140 changes: 127 additions & 13 deletions src/sage/categories/enumerated_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,31 @@ def __iter__(self):
else:
raise NotImplementedError("iterator called but not implemented")

def is_empty(self):
r"""
Return whether this set is empty.
EXAMPLES::
sage: F = FiniteEnumeratedSet([1,2,3])
sage: F.is_empty()
False
sage: F = FiniteEnumeratedSet([])
sage: F.is_empty()
True
TESTS::
sage: F.is_empty.__module__
'sage.categories.enumerated_sets'
"""
try:
next(iter(self))
except StopIteration:
return True
else:
return False

def list(self):
"""
Returns an error since the cardinality of self is not known.
Expand All @@ -248,7 +273,6 @@ def list(self):
raise NotImplementedError("unknown cardinality")
_list_default = list # needed by the check system.


def _first_from_iterator(self):
"""
The "first" element of ``self``.
Expand All @@ -264,8 +288,7 @@ def _first_from_iterator(self):
sage: C.first() # indirect doctest
1
"""
it = self.__iter__()
return next(it)
return next(iter(self))
first = _first_from_iterator

def _next_from_iterator(self, obj):
Expand Down Expand Up @@ -301,7 +324,7 @@ def _unrank_from_iterator(self, r):
"""
The ``r``-th element of ``self``
``self.unrank(r)`` returns the ``r``-th element of self where
``self.unrank(r)`` returns the ``r``-th element of self, where
``r`` is an integer between ``0`` and ``n-1`` where ``n`` is the
cardinality of ``self``.
Expand Down Expand Up @@ -332,7 +355,7 @@ def _rank_from_iterator(self, x):
"""
The rank of an element of ``self``
``self.unrank(x)`` returns the rank of `x`, that is its
``self.rank(x)`` returns the rank of `x`, that is its
position in the enumeration of ``self``. This is an
integer between ``0`` and ``n-1`` where ``n`` is the
cardinality of ``self``, or None if `x` is not in `self`.
Expand Down Expand Up @@ -694,14 +717,105 @@ class CartesianProducts(CartesianProductsCategory):
class ParentMethods:
def __iter__(self):
r"""
Iterates over the elements of self.
Return a lexicographic iterator for the elements of this cartesian product.
EXAMPLES::
sage: A = FiniteEnumeratedSets()(["a", "b"])
sage: B = FiniteEnumeratedSets().example(); B
An example of a finite enumerated set: {1,2,3}
sage: C = cartesian_product([A, B, A]); C
The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'})
sage: C in FiniteEnumeratedSets()
True
sage: list(C)
[('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'),
('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')]
sage: C.__iter__.__module__
'sage.categories.enumerated_sets'
sage: F22 = GF(2).cartesian_product(GF(2))
sage: list(F22)
[(0, 0), (0, 1), (1, 0), (1, 1)]
EXAMPLE::
sage: C = cartesian_product([Permutations(10)]*4)
sage: it = iter(C)
sage: it.next()
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
sage: it.next()
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[1, 2, 3, 4, 5, 6, 7, 8, 10, 9])
.. WARNING::
The elements are returned in lexicographic order,
which gives a valid enumeration only if all
factors, but possibly the first one, are
finite. So the following one is fine::
sage: it = iter(cartesian_product([ZZ, GF(2)]))
sage: [it.next() for _ in range(10)]
[(0, 0), (0, 1), (1, 0), (1, 1),
(-1, 0), (-1, 1), (2, 0), (2, 1),
(-2, 0), (-2, 1)]
But this one is not::
sage: it = iter(cartesian_product([GF(2), ZZ]))
sage: [it.next() for _ in range(10)]
doctest:...: UserWarning: Sage is not able to determine
whether the factors of this cartesian product are
finite. The lexicographic ordering might not go through
all elements.
[(0, 0), (0, 1), (0, -1), (0, 2), (0, -2),
(0, 3), (0, -3), (0, 4), (0, -4), (0, 5)]
.. NOTE::
Here it would be faster to use :func:`itertools.product` for sets
of small size. But the latter expands all factor in memory!
So we can not reasonably use it in general.
ALGORITHM:
Recipe 19.9 in the Python Cookbook by Alex Martelli
and David Ascher.
"""
if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]):
from warnings import warn
warn("Sage is not able to determine whether the factors of "
"this cartesian product are finite. The lexicographic "
"ordering might not go through all elements.")

# visualize an odometer, with "wheels" displaying "digits"...:
factors = list(self.cartesian_factors())
wheels = map(iter, factors)
digits = [next(it) for it in wheels]
while True:
yield self._cartesian_product_of_elements(digits)
for i in range(len(digits)-1, -1, -1):
try:
digits[i] = next(wheels[i])
break
except StopIteration:
wheels[i] = iter(factors[i])
digits[i] = next(wheels[i])
else:
break

def first(self):
r"""
Return the first element.
sage: F33 = GF(2).cartesian_product(GF(2))
sage: list(F33)
[(0, 0), (0, 1), (1, 0), (1, 1)]
EXAMPLES::
sage: cartesian_product([ZZ]*10).first()
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
"""
from itertools import product
for x in product(*self._sets):
yield self._cartesian_product_of_elements(x)
return self._cartesian_product_of_elements(
tuple(c.first() for c in self.cartesian_factors()))
Loading

0 comments on commit 04d92fa

Please sign in to comment.