Repository: projectmesa/mesa
Pull request #862 (2020-06-23 14:45:33)

Fix empty/None `variable_parameters` argument to BatchRunner (#861)
See #861.

An alternative approach would be not to touch `ParameterProduct`, but check inside `BatchRunner.__init__` if `variable_parameters` is empty and, if so, pass an empty list instead of `ParameterProduct` instance to `super().__init__()`.

There is an extra fix to handle batch runs with no `variable_parameters` and no `fixed_parameters` in `FixedBatchRunner._make_model_args`.


Patch:
diff --git a/mesa/batchrunner.py b/mesa/batchrunner.py
index 69be7612..75b46682 100644
--- a/mesa/batchrunner.py
+++ b/mesa/batchrunner.py
@@ -141,6 +141,10 @@ class FixedBatchRunner:
             kwargs = self.fixed_parameters.copy()
             all_kwargs.append(kwargs)
             all_param_values.append(kwargs.values())
+        else:
+            count = 1
+            all_kwargs.append({})
+            all_param_values.append([])
 
         total_iterations *= count
 
@@ -253,10 +257,14 @@ class FixedBatchRunner:
 # This is kind of a useless class, but it does carry the 'source' parameters with it
 class ParameterProduct:
     def __init__(self, variable_parameters):
-        self.param_names, self.param_lists = zip(
-            *(copy.deepcopy(variable_parameters)).items()
-        )
-        self._product = product(*self.param_lists)
+        if variable_parameters:
+            self.param_names, self.param_lists = zip(
+                *(copy.deepcopy(variable_parameters)).items()
+            )
+            self._product = product(*self.param_lists)
+        else:
+            self.param_names, self.param_lists = (), ()
+            self._product = iter([])
 
     def __iter__(self):
         return self


Test patch:
diff --git a/tests/test_batchrunner.py b/tests/test_batchrunner.py
index ed10e2bd..d0204c7b 100644
--- a/tests/test_batchrunner.py
+++ b/tests/test_batchrunner.py
@@ -108,7 +108,7 @@ class TestBatchRunner(unittest.TestCase):
         """
         Returns total number of batch runner's iterations.
         """
-        return reduce(mul, map(len, self.variable_params.values())) * self.iterations
+        return reduce(mul, map(len, self.variable_params.values()), 1) * self.iterations
 
     def test_model_level_vars(self):
         """
@@ -171,6 +171,36 @@ class TestBatchRunner(unittest.TestCase):
             model_vars["reported_fixed_param"].iloc[0], self.fixed_params["fixed_name"]
         )
 
+    def test_model_with_no_variable_parameters(self):
+        """
+        Test that model with no variable parameters is properly handled
+        """
+        self.variable_params = {}
+        self.fixed_params = {
+            "variable_model_param": 1,
+            "variable_agent_param": 1,
+        }
+        batch = self.launch_batch_processing()
+        model_vars = batch.get_model_vars_dataframe()
+        agent_vars = batch.get_agent_vars_dataframe()
+
+        self.assertEqual(len(model_vars) * NUM_AGENTS, len(agent_vars))
+        self.assertEqual(len(model_vars), self.model_runs)
+
+    def test_model_with_no_variable_and_no_fixed_parameters(self):
+        """
+        Test that model with no variable and no fixed parameters is properly handled
+        """
+        self.mock_model = lambda: MockModel(1, 1)
+        self.variable_params = None
+        self.fixed_params = None
+        batch = self.launch_batch_processing()
+        model_vars = batch.get_model_vars_dataframe()
+        agent_vars = batch.get_agent_vars_dataframe()
+
+        self.assertEqual(len(model_vars) * NUM_AGENTS, len(agent_vars))
+        self.assertEqual(len(model_vars), self.iterations)
+
 
 class TestParameters(unittest.TestCase):
     def test_product(self):