Skip to content

Make CellCollection.select() raise on invalid float at_most - #3860

Merged
quaquel merged 1 commit into
mesa:mainfrom
Dev-next-gen:fix/cellcollection-select-at-most
Sep 15, 2026
Merged

quaquel merged 1 commit into
mesa:mainfrom
Dev-next-gen:fix/cellcollection-select-at-most

Conversation

@Dev-next-gen

Copy link
Copy Markdown
Contributor

Pre-PR Checklist

  • This PR is a bug fix, not a new feature or enhancement.

Summary

CellCollection.select() still has the fraction check that #3853 just fixed in AbstractAgentSet.select(), so invalid float values for at_most are silently accepted on cell collections: nan selects every cell, 1.5 selects two cells, and -0.5 or 0.0 select none.

Bug / Issue

Same root cause as #3852, in mesa/discrete_space/cell_collection.py:

if at_most <= 1.0 and isinstance(at_most, float):
    at_most = int(len(self) * at_most)

nan <= 1.0 is False, so NaN skips the branch and reaches the generator, where count >= at_most is never true and the loop never stops. A float above 1.0 also skips the branch and ends up used as a raw count, so at_most=1.5 gives 2 cells rather than an error. Negative floats and 0.0 go through the branch and floor to a value that selects nothing.

On a collection of 10 cells at current main I get nan -> 10, 1.5 -> 2, -0.5 -> 0, 0.0 -> 0.

Implementation

I mirrored the check from #3853: on the float path (excluding the inf default), at_most must be in (0.0, 1.0], otherwise ValueError is raised. Integers and valid fractions are unchanged, and the docstring gets a Raises: entry.

Testing

I added the same four cases as #3853 (nan, 0.0, -0.5, 1.5) to the existing test_cell_collection. With only the test applied it fails with DID NOT RAISE ValueError; with the fix it passes. After the fix, 1.0 -> 10, 0.5 -> 5 and 3 -> 3 still behave as before. The whole tests/discrete_space directory passes (100 tests), and ruff check and ruff format --check are clean on both files.

Additional Notes

This makes CellCollection.select() and AgentSet.select() consistent again. I didn't find any caller inside the repo that passes a float above 1.0 to CellCollection.select().

Found by a defect-hunting pipeline I build and run (Dev-next-gen), using Claude Code with Anthropic's Claude Opus 5.

CellCollection.select() had the same fraction check that mesa#3853 fixed in
AbstractAgentSet.select(): `at_most <= 1.0 and isinstance(at_most, float)`.
With at_most=float("nan") the branch is skipped and the generator's
`count >= at_most` never becomes true, so every cell is selected. A float
above 1.0 such as 1.5 is used as a raw count (2 cells), and negative
floats or 0.0 silently select nothing.

Validate the (0.0, 1.0] range on the float path and raise ValueError
otherwise, matching AgentSet.select().
@github-actions github-actions Bot added the testing Release notes label label Sep 15, 2026
@quaquel quaquel added the bug Release notes label label Sep 15, 2026
@github-actions

Copy link
Copy Markdown

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🟢 -5.3% [-6.3%, -4.3%] 🔵 -0.2% [-0.4%, -0.0%]
BoltzmannWealth large 🔵 -3.0% [-3.9%, -2.2%] 🟢 -7.1% [-9.7%, -4.4%]
Schelling small 🔵 -1.1% [-1.9%, -0.2%] 🔵 +0.1% [-0.5%, +0.8%]
Schelling large 🔵 +0.7% [+0.1%, +1.4%] 🔵 +2.6% [+1.4%, +3.7%]
WolfSheep small 🔵 +2.0% [+1.4%, +2.6%] 🔴 +9.0% [+4.4%, +13.6%]
WolfSheep large 🔵 +2.3% [+1.1%, +3.5%] 🔵 +3.5% [+2.5%, +4.5%]
SugarscapeG1mt small 🔵 +2.5% [+1.6%, +3.4%] 🔵 +2.5% [+0.6%, +4.2%]
SugarscapeG1mt large 🔴 +5.7% [+3.8%, +7.6%] 🔵 +3.3% [+1.7%, +4.6%]
BoidFlockers small 🔵 +0.8% [+0.4%, +1.3%] 🔵 -0.5% [-0.8%, -0.2%]
BoidFlockers large 🔵 +2.4% [+1.4%, +3.2%] 🔵 +0.2% [+0.0%, +0.4%]
MultiLevelAllianceModel small 🔴 +6.9% [+5.4%, +8.7%] 🔵 +1.7% [+0.8%, +2.4%]
MultiLevelAllianceModel large 🔵 +3.5% [+2.3%, +4.7%] 🔵 +4.5% [+2.5%, +6.7%]

@quaquel
quaquel merged commit 730b7fd into mesa:main Sep 15, 2026
17 of 18 checks passed
rishiiicreates added a commit to rishiiicreates/mesa that referenced this pull request Oct 3, 2026
Following mesa#3853 and mesa#3860, negative integers (which previously silently
returned an empty set) and booleans (which were treated as 0 or 1 because
isinstance(bool, int) is True) were not validated.

Now AbstractAgentSet.select() and CellCollection.select():
- Raise TypeError if at_most is a boolean or not int/float.
- Raise ValueError if at_most is an integer < 0.

Fixes mesa#3867
EwoutH pushed a commit that referenced this pull request Oct 28, 2026
CellCollection.select() had the same fraction check that #3853 fixed in
AbstractAgentSet.select(): `at_most <= 1.0 and isinstance(at_most, float)`.
With at_most=float("nan") the branch is skipped and the generator's
`count >= at_most` never becomes true, so every cell is selected. A float
above 1.0 such as 1.5 is used as a raw count (2 cells), and negative
floats or 0.0 silently select nothing.

Validate the (0.0, 1.0] range on the float path and raise ValueError
otherwise, matching AgentSet.select().

(cherry picked from commit 730b7fd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Release notes label testing Release notes label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants