Skip to content

Commit fbb3237

Browse files
author
Release Manager
committed
gh-39443: Implement correct iteration through disjoint enumerated set for infinite set Previously, the iteration does not touch any finite set that comes after an infinite set. This fixes the issue. It tries to not change the existing behavior whenever all element sets are finite by checking for finiteness. Meanwhile fix a genuine bug in `root_lattice_realizations` where `DisjointUnionEnumeratedSets([self.positive_real_roots(), self.positive_imaginary_roots()])` is returned, but both sets are infinite, so iterating through it will never reach any element in the second set. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39443 Reported by: user202729 Reviewer(s): Travis Scrimshaw, user202729
2 parents 68e3791 + d1e55d0 commit fbb3237

File tree

3 files changed

+137
-24
lines changed

3 files changed

+137
-24
lines changed

src/sage/categories/modules_with_basis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2608,7 +2608,7 @@ def _an_element_(self):
26082608
B[()] + B[(1,2)] + 3*B[(1,2,3)] + 2*B[(1,3,2)]
26092609
sage: ABA = cartesian_product((A, B, A))
26102610
sage: ABA.an_element() # indirect doctest
2611-
2*B[(0, word: )] + 2*B[(0, word: a)] + 3*B[(0, word: b)]
2611+
2*B[(0, word: )] + 2*B[(1, ())] + 3*B[(1, (1,3,2))]
26122612
"""
26132613
from .cartesian_product import cartesian_product
26142614
return cartesian_product([module.an_element() for module in self.modules])

src/sage/combinat/root_system/root_lattice_realizations.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -694,14 +694,14 @@ def positive_roots(self, index_set=None):
694694
sage: [PR.unrank(i) for i in range(10)] # needs sage.graphs
695695
[alpha[1],
696696
alpha[2],
697+
alpha[0] + alpha[1] + alpha[2] + alpha[3],
697698
alpha[3],
699+
2*alpha[0] + 2*alpha[1] + 2*alpha[2] + 2*alpha[3],
698700
alpha[1] + alpha[2],
701+
3*alpha[0] + 3*alpha[1] + 3*alpha[2] + 3*alpha[3],
699702
alpha[2] + alpha[3],
700-
alpha[1] + alpha[2] + alpha[3],
701-
alpha[0] + 2*alpha[1] + alpha[2] + alpha[3],
702-
alpha[0] + alpha[1] + 2*alpha[2] + alpha[3],
703-
alpha[0] + alpha[1] + alpha[2] + 2*alpha[3],
704-
alpha[0] + 2*alpha[1] + 2*alpha[2] + alpha[3]]
703+
4*alpha[0] + 4*alpha[1] + 4*alpha[2] + 4*alpha[3],
704+
alpha[1] + alpha[2] + alpha[3]]
705705
"""
706706
if self.cartan_type().is_affine():
707707
from sage.sets.disjoint_union_enumerated_sets \
@@ -798,18 +798,18 @@ def positive_real_roots(self):
798798
sage: [PR.unrank(i) for i in range(5)] # needs sage.graphs
799799
[alpha[1],
800800
alpha[2],
801+
2*alpha[0] + 2*alpha[1] + alpha[2],
801802
alpha[1] + alpha[2],
802-
2*alpha[1] + alpha[2],
803-
alpha[0] + alpha[1] + alpha[2]]
803+
4*alpha[0] + 4*alpha[1] + 2*alpha[2]]
804804
805805
sage: Q = RootSystem(['D',3,2]).root_lattice()
806806
sage: PR = Q.positive_roots() # needs sage.graphs
807807
sage: [PR.unrank(i) for i in range(5)] # needs sage.graphs
808808
[alpha[1],
809809
alpha[2],
810+
alpha[0] + alpha[1] + alpha[2],
810811
alpha[1] + 2*alpha[2],
811-
alpha[1] + alpha[2],
812-
alpha[0] + alpha[1] + 2*alpha[2]]
812+
2*alpha[0] + 2*alpha[1] + 2*alpha[2]]
813813
"""
814814
if self.cartan_type().is_finite():
815815
return tuple(RecursivelyEnumeratedSet(self.simple_roots(),

src/sage/sets/disjoint_union_enumerated_sets.py

Lines changed: 127 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -392,32 +392,145 @@ def __iter__(self):
392392
"""
393393
TESTS::
394394
395+
sage: from itertools import islice
395396
sage: U4 = DisjointUnionEnumeratedSets(
396397
....: Family(NonNegativeIntegers(), Permutations))
397-
sage: it = iter(U4)
398-
sage: [next(it), next(it), next(it), next(it), next(it), next(it)]
398+
sage: list(islice(iter(U4), 6))
399399
[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]
400400
401401
sage: # needs sage.combinat
402402
sage: U4 = DisjointUnionEnumeratedSets(
403403
....: Family(NonNegativeIntegers(), Permutations),
404404
....: keepkey=True, facade=False)
405-
sage: it = iter(U4)
406-
sage: [next(it), next(it), next(it), next(it), next(it), next(it)]
407-
[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2])]
408-
sage: el = next(it); el.parent() == U4
409-
True
410-
sage: el.value == (3, Permutation([2,1,3]))
405+
sage: l = list(islice(iter(U4), 7)); l
406+
[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2]), (3, [2, 1, 3])]
407+
sage: l[-1].parent() is U4
411408
True
409+
410+
Check when both the set of keys and each element set is finite::
411+
412+
sage: list(DisjointUnionEnumeratedSets(
413+
....: Family({1: FiniteEnumeratedSet([1,2,3]),
414+
....: 2: FiniteEnumeratedSet([4,5,6])})))
415+
[1, 2, 3, 4, 5, 6]
416+
417+
Check when the set of keys is finite but each element set is infinite::
418+
419+
sage: list(islice(DisjointUnionEnumeratedSets(
420+
....: Family({1: NonNegativeIntegers(),
421+
....: 2: NonNegativeIntegers()}), keepkey=True), 0, 10))
422+
[(1, 0), (1, 1), (2, 0), (1, 2), (2, 1), (1, 3), (2, 2), (1, 4), (2, 3), (1, 5)]
423+
424+
Check when the set of keys is infinite but each element set is finite::
425+
426+
sage: list(islice(DisjointUnionEnumeratedSets(
427+
....: Family(NonNegativeIntegers(), lambda x: FiniteEnumeratedSet(range(x))),
428+
....: keepkey=True), 0, 20))
429+
[(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2), (4, 0), (4, 1), (4, 2), (4, 3),
430+
(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4)]
431+
432+
Check when some element sets are empty (note that if there are infinitely many sets
433+
but only finitely many elements in total, the iteration will hang)::
434+
435+
sage: list(DisjointUnionEnumeratedSets(
436+
....: Family({1: FiniteEnumeratedSet([]),
437+
....: 2: FiniteEnumeratedSet([]),
438+
....: 3: FiniteEnumeratedSet([]),
439+
....: 4: FiniteEnumeratedSet([]),
440+
....: 5: FiniteEnumeratedSet([1,2,3]),
441+
....: 6: FiniteEnumeratedSet([4,5,6])})))
442+
[1, 2, 3, 4, 5, 6]
443+
444+
Check when there's one infinite set and infinitely many finite sets::
445+
446+
sage: list(islice(DisjointUnionEnumeratedSets(
447+
....: Family(NonNegativeIntegers(), lambda x: FiniteEnumeratedSet([]) if x else NonNegativeIntegers())),
448+
....: 0, 10))
449+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
450+
451+
The following cannot be determined to be finite, but the first elements can still be retrieved::
452+
453+
sage: U = DisjointUnionEnumeratedSets(
454+
....: Family(NonNegativeIntegers(), lambda x: FiniteEnumeratedSet([] if x >= 2 else [1, 2])),
455+
....: keepkey=True)
456+
sage: list(U) # not tested
457+
sage: list(islice(iter(U), 5)) # not tested, hangs
458+
sage: list(islice(iter(U), 4))
459+
[(0, 1), (0, 2), (1, 1), (1, 2)]
460+
461+
Coverage test in case some element sets does not know it is finite
462+
but correctly raises :exc:`StopIteration` during iteration::
463+
464+
sage: from sage.sets.set import Set_object
465+
sage: class UnknowinglyFiniteSet(Set_object):
466+
....: def is_finite(self):
467+
....: return False
468+
sage: [*UnknowinglyFiniteSet([1, 2, 3])]
469+
[1, 2, 3]
470+
sage: [*iter(UnknowinglyFiniteSet([1, 2, 3]))]
471+
[1, 2, 3]
472+
sage: list(islice(DisjointUnionEnumeratedSets(
473+
....: (UnknowinglyFiniteSet(frozenset([1,2,3])), UnknowinglyFiniteSet(frozenset([4,5,6])))), 7))
474+
[1, 2, 4, 3, 5, 6]
412475
"""
413-
for k in self._family.keys():
414-
for el in self._family[k]:
476+
def wrap_element(el, k):
477+
nonlocal self
478+
if self._keepkey:
479+
el = (k, el)
480+
if self._facade:
481+
return el
482+
else:
483+
return self.element_class(self, el) # Bypass correctness tests
484+
485+
keys_iter = iter(self._family.keys())
486+
if self._keepkey:
487+
seen_keys = []
488+
el_iters = []
489+
while keys_iter is not None or el_iters:
490+
if keys_iter is not None:
491+
try:
492+
k = next(keys_iter)
493+
except StopIteration:
494+
keys_iter = None
495+
if keys_iter is not None:
496+
el_set = self._family[k]
497+
try:
498+
is_finite = el_set.is_finite()
499+
except (AttributeError, TypeError, NotImplementedError):
500+
is_finite = False
501+
if is_finite:
502+
for el in el_set:
503+
yield wrap_element(el, k)
504+
else:
505+
el_iters.append(iter(el_set))
506+
if self._keepkey:
507+
seen_keys.append(k)
508+
any_stopped = False
509+
for i, obj in enumerate(zip(seen_keys, el_iters) if self._keepkey else el_iters):
510+
if self._keepkey:
511+
k, el_iter = obj
512+
else:
513+
k = None
514+
el_iter = obj
515+
try:
516+
el = next(el_iter)
517+
except StopIteration:
518+
el_iters[i] = None
519+
any_stopped = True
520+
continue
521+
yield wrap_element(el, k)
522+
if any_stopped:
415523
if self._keepkey:
416-
el = (k, el)
417-
if self._facade:
418-
yield el
524+
filtered = list(zip(
525+
*[(k, el_iter) for k, el_iter in zip(seen_keys, el_iters) if el_iter is not None]))
526+
if filtered:
527+
seen_keys = list(filtered[0])
528+
el_iters = list(filtered[1])
529+
else:
530+
seen_keys = []
531+
el_iters = []
419532
else:
420-
yield self.element_class(self, el) # Bypass correctness tests
533+
el_iters = [el_iter for el_iter in el_iters if el_iter is not None]
421534

422535
def an_element(self):
423536
"""

0 commit comments

Comments
 (0)