Repository: projectmesa/mesa
Pull request #933 (2020-10-25 20:47:39)

fix variables names, mp function locations, datacollector
Sorry, I am getting crushed helping students with finals so instead of trying to debug why this isn't updating the previous pull request I went with a new one, just to keep the momentum.


Patch:
diff --git a/mesa/batchrunner.py b/mesa/batchrunner.py
index cd87d328..f520d86c 100644
--- a/mesa/batchrunner.py
+++ b/mesa/batchrunner.py
@@ -145,35 +145,6 @@ class FixedBatchRunner:
 
         return total_iterations, all_kwargs, all_param_values
 
-    def _make_model_args_mp(self):
-        """Prepare all combinations of parameter values for `run_all`
-        Due to multiprocessing requirements of @StaticMethod takes different input, hence the similar function
-        Returns:
-            List of list with the form:
-            [[model_object, dictionary_of_kwargs, max_steps, iterations]]
-        """
-        total_iterations = self.iterations
-        all_kwargs = []
-
-        _count = len(self.parameters_list)
-        if _count:
-            for params in self.parameters_list:
-                kwargs = params.copy()
-                kwargs.update(self.fixed_parameters)
-                # run each iterations specific number of times
-                for _iter in range(self.iterations):
-                    kwargs_repeated = kwargs.copy()
-                    all_kwargs.append([self.model_cls, kwargs_repeated, self.max_steps, _iter])
-
-        elif len(self.fixed_parameters):
-            _count = 1
-            kwargs = self.fixed_parameters.copy()
-            all_kwargs.append(kwargs)
-
-        total_iterations *= _count
-
-        return all_kwargs, total_iterations
-
     def run_all(self):
         """ Run the model at all parameter combinations and store results. """
         run_count = count()
@@ -212,49 +183,6 @@ class FixedBatchRunner:
                 getattr(self, "datacollector_model_reporters", None),
                 getattr(self, "datacollector_agent_reporters", None))
 
-    @staticmethod
-    def _run_wrappermp(iter_args):
-        """
-        Based on requirement of Python multiprocessing requires @staticmethod decorator;
-        this is primarily to ensure functionality on Windows OS and doe not impact MAC or Linux distros
-
-        :param iter_args: List of arguments for model run
-            iter_args[0] = model object
-            iter_args[1] = key word arguments needed for model object
-            iter_args[2] = maximum number of steps for model
-            iter_args[3] = number of time to run model for stochastic/random variation with same parameters
-        :return:
-            tuple of param values which serves as a unique key for model results
-            model object
-        """
-
-        _model_i = iter_args[0]
-        _kwargs = iter_args[1]
-        _max_steps = iter_args[2]
-        _iteration = iter_args[3]
-
-        def run_iteration_mp(_model_i, _kwargs, _max_steps, _iteration):
-            """
-            :type _model_i: object of model
-            :type _kwargs: dict of keyword argument dictionary for model
-            :type _max_steps: int of number of steps
-            :type _iteration: int of number of iterations
-            """
-            # instantiate version of model with correct parameters
-            model = _model_i(**_kwargs)
-            while model.running and model.schedule.steps < _max_steps:
-                model.step()
-
-            # add iteration number to dictionary to make unique_key
-            _kwargs["iteration"] = _iteration
-
-            # convert kwargs dict to tuple to  make consistent
-            _param_values = tuple(_kwargs.values())
-
-            return _param_values, model
-
-        return run_iteration_mp(_model_i, _kwargs, _max_steps, _iteration)
-
     def run_model(self, model):
         """ Run a model object to completion, or until reaching max steps.
 
@@ -343,8 +271,8 @@ class FixedBatchRunner:
                 val = self.fixed_parameters[param]
 
                 # avoid error when val is an iterable
-                _vallist = [val for i in range(ordered.shape[0])]
-                ordered[param] = _vallist
+                vallist = [val for i in range(ordered.shape[0])]
+                ordered[param] = vallist
         return ordered
 
 
@@ -496,6 +424,69 @@ class BatchRunnerMP(BatchRunner):
         super().__init__(model_cls, **kwargs)
         self.pool = Pool(self.processes)
 
+    def _make_model_args_mp(self):
+        """Prepare all combinations of parameter values for `run_all`
+        Due to multiprocessing requirements of @StaticMethod takes different input, hence the similar function
+        Returns:
+            List of list with the form:
+            [[model_object, dictionary_of_kwargs, max_steps, iterations]]
+        """
+        total_iterations = self.iterations
+        all_kwargs = []
+
+        count = len(self.parameters_list)
+        if count:
+            for params in self.parameters_list:
+                kwargs = params.copy()
+                kwargs.update(self.fixed_parameters)
+                # run each iterations specific number of times
+                for iter in range(self.iterations):
+                    kwargs_repeated = kwargs.copy()
+                    all_kwargs.append([self.model_cls, kwargs_repeated, self.max_steps, iter])
+
+        elif len(self.fixed_parameters):
+            count = 1
+            kwargs = self.fixed_parameters.copy()
+            all_kwargs.append(kwargs)
+
+        total_iterations *= count
+
+        return all_kwargs, total_iterations
+
+    @staticmethod
+    def _run_wrappermp(iter_args):
+        """
+        Based on requirement of Python multiprocessing requires @staticmethod decorator;
+        this is primarily to ensure functionality on Windows OS and does not impact MAC or Linux distros
+
+        :param iter_args: List of arguments for model run
+            iter_args[0] = model object
+            iter_args[1] = key word arguments needed for model object
+            iter_args[2] = maximum number of steps for model
+            iter_args[3] = number of time to run model for stochastic/random variation with same parameters
+        :return:
+            tuple of param values which serves as a unique key for model results
+            model object
+        """
+
+        model_i = iter_args[0]
+        kwargs = iter_args[1]
+        max_steps = iter_args[2]
+        iteration = iter_args[3]
+
+        # instantiate version of model with correct parameters
+        model = model_i(**kwargs)
+        while model.running and model.schedule.steps < max_steps:
+            model.step()
+
+        # add iteration number to dictionary to make unique_key
+        kwargs["iteration"] = iteration
+
+        # convert kwargs dict to tuple to  make consistent
+        param_values = tuple(kwargs.values())
+
+        return param_values, model
+
     def _result_prep_mp(self, results):
         """
         Helper Function
diff --git a/mesa/datacollection.py b/mesa/datacollection.py
index 349fe26c..836f4b6b 100644
--- a/mesa/datacollection.py
+++ b/mesa/datacollection.py
@@ -171,6 +171,9 @@ class DataCollector:
         agent_records = map(get_reports, model.schedule.agents)
         return agent_records
 
+    def _reporter_decorator(self, reporter):
+        return reporter()
+
     def collect(self, model):
         """ Collect all the data for the given model object. """
         if self.model_reporters:
@@ -186,7 +189,7 @@ class DataCollector:
                 elif isinstance(reporter, list):
                     self.model_vars[var].append(reporter[0](*reporter[1]))
                 else:
-                    self.model_vars[var].append(reporter)
+                    self.model_vars[var].append(self._reporter_decorator(reporter))
 
         if self.agent_reporters:
             agent_records = self._record_agents(model)


Test patch:
diff --git a/tests/test_batchrunner.py b/tests/test_batchrunner.py
index 24de7015..86caa667 100644
--- a/tests/test_batchrunner.py
+++ b/tests/test_batchrunner.py
@@ -50,7 +50,7 @@ class MockModel(Model):
         self.fixed_model_param = fixed_model_param
         self.n_agents = kwargs.get("n_agents", NUM_AGENTS)
         self.datacollector = DataCollector(model_reporters={
-            "reported_model_param": self.get_local_model_param()},
+            "reported_model_param": self.get_local_model_param},
             agent_reporters={"agent_id": "unique_id", "agent_local": "local"})
         self.running = True
         self.init_agents()
@@ -170,7 +170,7 @@ class TestBatchRunner(unittest.TestCase):
         )
 
         with self.assertRaises(KeyError):
-            agent_collector[(900, "k" , 3)]
+            agent_collector[(900, "k", 3)]
 
     def test_model_with_fixed_parameters_as_kwargs(self):
         """
diff --git a/tests/test_batchrunnerMP.py b/tests/test_batchrunnerMP.py
index 1a45e171..d3330b72 100644
--- a/tests/test_batchrunnerMP.py
+++ b/tests/test_batchrunnerMP.py
@@ -50,13 +50,13 @@ class MockModel(Model):
         self.fixed_model_param = fixed_model_param
         self.n_agents = kwargs.get("n_agents", NUM_AGENTS)
         self.datacollector = DataCollector(model_reporters={
-            "reported_model_param": self.get_local_model_param(1)},
+            "reported_model_param": self.get_local_model_param},
             agent_reporters={"agent_id": "unique_id", "agent_local": "local"})
         self.running = True
         self.init_agents()
 
-    def get_local_model_param(self, add):
-        return 41 + add
+    def get_local_model_param(self):
+        return 42
 
     def init_agents(self):
         for i in range(self.n_agents):
diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py
index 03c02af8..b95d488d 100644
--- a/tests/test_datacollector.py
+++ b/tests/test_datacollector.py
@@ -51,7 +51,7 @@ class MockModel(Model):
             {
                 "total_agents": lambda m: m.schedule.get_agent_count(),
                 "model_value": "model_val",
-                "model_calc": self.schedule.get_agent_count(),
+                "model_calc": self.schedule.get_agent_count,
                 "model_calc_comp": [self.test_model_calc_comp, [3, 4]],
                 "model_calc_fail": [self.test_model_calc_comp, [12, 0]]
             },
@@ -78,11 +78,21 @@ class TestDataCollector(unittest.TestCase):
         """
         self.model = MockModel()
         for i in range(7):
+            if i == 4:
+                self.model.schedule.remove(self.model.schedule._agents[3])
             self.model.step()
+
         # Write to table:
         for agent in self.model.schedule.agents:
             agent.write_final_values()
 
+    def step_assertion(self, model_var):
+        for element in model_var:
+            if model_var.index(element) < 4:
+                assert element == 10
+            else:
+                assert element == 9
+
     def test_model_vars(self):
         """
         Test model-level variable collection.
@@ -97,12 +107,10 @@ class TestDataCollector(unittest.TestCase):
         assert len(data_collector.model_vars["model_value"]) == 7
         assert len(data_collector.model_vars["model_calc"]) == 7
         assert len(data_collector.model_vars["model_calc_comp"]) == 7
-        for element in data_collector.model_vars["total_agents"]:
-            assert element == 10
+        self.step_assertion(data_collector.model_vars["total_agents"])
         for element in data_collector.model_vars["model_value"]:
             assert element == 100
-        for element in data_collector.model_vars['model_calc']:
-            assert element == 10
+        self.step_assertion((data_collector.model_vars["model_calc"]))
         for element in data_collector.model_vars['model_calc_comp']:
             assert element == 75
         for element in data_collector.model_vars["model_calc_fail"]:
@@ -117,7 +125,11 @@ class TestDataCollector(unittest.TestCase):
 
         assert len(data_collector._agent_records) == 7
         for step, records in data_collector._agent_records.items():
-            assert len(records) == 10
+            if step < 5:
+                assert len(records) == 10
+            else:
+                assert len(records) == 9
+
             for values in records:
                 assert len(values) == 4
 
@@ -137,7 +149,7 @@ class TestDataCollector(unittest.TestCase):
         assert "agent_id" in data_collector.tables["Final_Values"]
         assert "final_value" in data_collector.tables["Final_Values"]
         for key, data in data_collector.tables["Final_Values"].items():
-            assert len(data) == 10
+            assert len(data) == 9
 
         with self.assertRaises(Exception):
             data_collector.add_table_row("error_table", {})
@@ -154,8 +166,8 @@ class TestDataCollector(unittest.TestCase):
         agent_vars = data_collector.get_agent_vars_dataframe()
         table_df = data_collector.get_table_dataframe("Final_Values")
         assert model_vars.shape == (7, 5)
-        assert agent_vars.shape == (70, 2)
-        assert table_df.shape == (10, 2)
+        assert agent_vars.shape == (67, 2)
+        assert table_df.shape == (9, 2)
 
         with self.assertRaises(Exception):
             table_df = data_collector.get_table_dataframe("not a real table")