Repository: projectmesa/mesa
Pull request #2523 (2024-11-29 16:17:59)

added empty iterable checks and updated tests
## Summary
Fixed the handling of empty iterables in _make_model_kwargs. Now raises a ValueError when an empty list is passed.

## Bug / Issue
Closes https://github.com/projectmesa/mesa/issues/2108.
Empty iterables weren't properly handled.

## Implementation
Updated `_make_model_kwargs` to raise a ValueError for empty iterables.
added a new function `test_batch_run_with_params_with_empty_content()` for testing.

github-actions[bot]: Performance benchmarks:

| Model           | Size   | Init time [95% CI]      | Run time [95% CI]         |
|-----------------|--------|-------------------------|---------------------------|
| BoltzmannWealth | small  | 🔵 +4.5% [+2.8%, +6.1%] | 🔵 +0.3% [+0.1%, +0.5%]   |
| BoltzmannWealth | large  | 🔵 +0.2% [-0.5%, +1.0%] | 🔵 +3.2% [+1.1%, +4.9%]   |
| Schelling       | small  | 🔵 +0.9% [+0.6%, +1.1%] | 🔵 +0.7% [+0.4%, +0.9%]   |
| Schelling       | large  | 🔵 +0.6% [+0.2%, +0.9%] | 🔵 +5.0% [+2.5%, +7.2%]   |
| WolfSheep       | small  | 🔵 +1.8% [+1.5%, +2.2%] | 🔵 +0.7% [+0.4%, +1.0%]   |
| WolfSheep       | large  | 🔵 +2.4% [+1.0%, +4.0%] | 🔴 +10.7% [+5.8%, +15.9%] |
| BoidFlockers    | small  | 🔵 -0.5% [-1.1%, +0.1%] | 🔵 -0.3% [-1.3%, +0.5%]   |
| BoidFlockers    | large  | 🔵 -0.6% [-1.1%, +0.1%] | 🔵 -0.7% [-1.1%, -0.3%]   |

Sahil-Chhoker: Hey @quaquel can you please review the changes for this pull request.
quaquel: This is a part of the code base that I am not intimately familiar with, so I need some more time. I hope to get to it somewhere in the coming days.

Patch:
diff --git a/mesa/batchrunner.py b/mesa/batchrunner.py
index bc35d1cf..bcc4dffb 100644
--- a/mesa/batchrunner.py
+++ b/mesa/batchrunner.py
@@ -106,7 +106,14 @@ def _make_model_kwargs(
     Parameters
     ----------
     parameters : Mapping[str, Union[Any, Iterable[Any]]]
-        Single or multiple values for each model parameter name
+        Single or multiple values for each model parameter name.
+
+        Allowed values for each parameter:
+        - A single value (e.g., `32`, `"relu"`).
+        - A non-empty iterable (e.g., `[0.01, 0.1]`, `["relu", "sigmoid"]`).
+
+        Not allowed:
+        - Empty lists or empty iterables (e.g., `[]`, `()`, etc.). These should be removed manually.
 
     Returns:
     -------
@@ -118,6 +125,12 @@ def _make_model_kwargs(
         if isinstance(values, str):
             # The values is a single string, so we shouldn't iterate over it.
             all_values = [(param, values)]
+        elif isinstance(values, list | tuple | set) and len(values) == 0:
+            # If it's an empty iterable, raise an error
+            raise ValueError(
+                f"Parameter '{param}' contains an empty iterable, which is not allowed."
+            )
+
         else:
             try:
                 all_values = [(param, value) for value in values]


Test patch:
diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py
index c60232b5..aec13bc4 100644
--- a/tests/test_batch_run.py
+++ b/tests/test_batch_run.py
@@ -24,6 +24,37 @@ def test_make_model_kwargs():  # noqa: D103
     assert _make_model_kwargs({"a": "value"}) == [{"a": "value"}]
 
 
+def test_batch_run_with_params_with_empty_content():
+    """Test handling of empty iterables in model kwargs."""
+    # If "a" is a single value and "b" is an empty list (should raise error for the empty list)
+    parameters_with_empty_list = {
+        "a": 3,
+        "b": [],
+    }
+
+    try:
+        _make_model_kwargs(parameters_with_empty_list)
+        raise AssertionError(
+            "Expected ValueError for empty iterable but no error was raised."
+        )
+    except ValueError as e:
+        assert "contains an empty iterable" in str(e)
+
+    # If "a" is a iterable and "b" is an empty list (should still raise error)
+    parameters_with_empty_b = {
+        "a": [1, 2],
+        "b": [],
+    }
+
+    try:
+        _make_model_kwargs(parameters_with_empty_b)
+        raise AssertionError(
+            "Expected ValueError for empty iterable but no error was raised."
+        )
+    except ValueError as e:
+        assert "contains an empty iterable" in str(e)
+
+
 class MockAgent(Agent):
     """Minimalistic agent implementation for testing purposes."""