Repository: projectmesa/mesa
Pull request #2267 (2024-09-02 08:14:55)

Model: Replace `get_agents_of_type` method with `agents_by_type` property
This PR replaces the Model method `get_agents_of_type()` with an `agents_by_type` property, which directly returns the dict.

Instead of using:
```Python
model.get_agents_of_type(Sheep)
```
You should now use:
```Python
model.agents_of_type[Sheep]
```
Since we also use `agents` and not `get_agents`, it's more intuitive to directly have access to the object itself (in this case the `agents_by_type` dict). It's also is more concise and since you have full dict access, more flexible.

Examples and tests are updated and an deprecation warning is added for `get_agents_of_type()`.

github-actions[bot]: Performance benchmarks:

| Model           | Size   | Init time [95% CI]        | Run time [95% CI]       |
|-----------------|--------|---------------------------|-------------------------|
| BoltzmannWealth | small  | 🔵 -0.6% [-1.9%, +0.6%]   | 🔵 -0.6% [-0.8%, -0.5%] |
| BoltzmannWealth | large  | 🔵 -0.9% [-32.3%, +35.8%] | 🔵 -0.7% [-2.9%, +1.5%] |
| Schelling       | small  | 🔵 +0.1% [-0.3%, +0.4%]   | 🔵 +1.4% [+1.0%, +1.7%] |
| Schelling       | large  | 🔵 +0.6% [-0.4%, +1.4%]   | 🔵 +3.5% [+2.1%, +4.9%] |
| WolfSheep       | small  | 🔵 -2.0% [-3.1%, -0.8%]   | 🔵 -2.5% [-2.9%, -2.2%] |
| WolfSheep       | large  | 🔵 -1.7% [-2.2%, -1.1%]   | 🟢 -6.2% [-7.1%, -5.4%] |
| BoidFlockers    | small  | 🔵 -1.6% [-2.0%, -1.1%]   | 🔵 -0.5% [-1.2%, +0.2%] |
| BoidFlockers    | large  | 🔵 -2.3% [-3.0%, -1.6%]   | 🔵 -0.9% [-1.9%, +0.1%] |

EwoutH: Fair point. At this point we can just expose the `agents_by_type` dict though a property, right?
EwoutH: Implemented as suggested!
quaquel: If you add a test for this in test_model, its good to go as far as I am concerned. 
EwoutH: Tests are added and the PR description and title are updated with some explanation and examples.

Thanks for reviewing!

Patch:
diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py
index ecb78594..655d51bb 100644
--- a/benchmarks/WolfSheep/wolf_sheep.py
+++ b/benchmarks/WolfSheep/wolf_sheep.py
@@ -227,8 +227,8 @@ class WolfSheep(Model):
             patch.move_to(cell)
 
     def step(self):
-        self.get_agents_of_type(Sheep).shuffle(inplace=True).do("step")
-        self.get_agents_of_type(Wolf).shuffle(inplace=True).do("step")
+        self.agents_by_type[Sheep].shuffle(inplace=True).do("step")
+        self.agents_by_type[Wolf].shuffle(inplace=True).do("step")
 
 
 if __name__ == "__main__":
diff --git a/mesa/experimental/devs/examples/wolf_sheep.py b/mesa/experimental/devs/examples/wolf_sheep.py
index 9fa6b3d9..99fde6a9 100644
--- a/mesa/experimental/devs/examples/wolf_sheep.py
+++ b/mesa/experimental/devs/examples/wolf_sheep.py
@@ -231,8 +231,8 @@ class WolfSheep(mesa.Model):
             self.grid.place_agent(patch, pos)
 
     def step(self):
-        self.get_agents_of_type(Sheep).shuffle(inplace=True).do("step")
-        self.get_agents_of_type(Wolf).shuffle(inplace=True).do("step")
+        self.agents_by_type[Sheep].shuffle(inplace=True).do("step")
+        self.agents_by_type[Wolf].shuffle(inplace=True).do("step")
 
 
 if __name__ == "__main__":
diff --git a/mesa/model.py b/mesa/model.py
index 9387e6d1..f276cc82 100644
--- a/mesa/model.py
+++ b/mesa/model.py
@@ -33,11 +33,13 @@ class Model:
     Properties:
         agents: An AgentSet containing all agents in the model
         agent_types: A list of different agent types present in the model.
+        agents_by_type: A dictionary where the keys are agent types and the values are the corresponding AgentSets.
         steps: An integer representing the number of steps the model has taken.
                It increases automatically at the start of each step() call.
 
     Methods:
         get_agents_of_type: Returns an AgentSet of agents of the specified type.
+            Deprecated: Use agents_by_type[agenttype] instead.
         run_model: Runs the model's simulation until a defined end condition is reached.
         step: Executes a single step of the model's simulation process.
         next_id: Generates and returns the next unique identifier for an agent.
@@ -106,24 +108,26 @@ class Model:
         """Return a list of all unique agent types registered with the model."""
         return list(self._agents_by_type.keys())
 
-    def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
-        """Retrieves an AgentSet containing all agents of the specified type.
-
-        Args:
-            agenttype: The type of agent to retrieve.
-
-        Raises:
-            KeyError: If agenttype does not exist
-
+    @property
+    def agents_by_type(self) -> dict[type[Agent], AgentSet]:
+        """A dictionary where the keys are agent types and the values are the corresponding AgentSets."""
+        return self._agents_by_type
 
-        """
-        return self._agents_by_type[agenttype]
+    def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
+        """Deprecated: Retrieves an AgentSet containing all agents of the specified type."""
+        warnings.warn(
+            f"Model.get_agents_of_type() is deprecated, please replace get_agents_of_type({agenttype})"
+            f"with the property agents_by_type[{agenttype}].",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.agents_by_type[agenttype]
 
     def _setup_agent_registration(self):
         """helper method to initialize the agent registration datastructures"""
         self._agents = {}  # the hard references to all agents in the model
         self._agents_by_type: dict[
-            type, AgentSet
+            type[Agent], AgentSet
         ] = {}  # a dict with an agentset for each class of agents
         self._all_agents = AgentSet([], self)  # an agenset with all agents
 


Test patch:
diff --git a/tests/test_model.py b/tests/test_model.py
index 837d6b00..c92fcb2e 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -1,4 +1,4 @@
-from mesa.agent import Agent
+from mesa.agent import Agent, AgentSet
 from mesa.model import Model
 
 
@@ -53,3 +53,19 @@ def test_agent_types():
     test_agent = TestAgent(model.next_id(), model)
     assert test_agent in model.agents
     assert type(test_agent) in model.agent_types
+
+
+def test_agents_by_type():
+    class Wolf(Agent):
+        pass
+
+    class Sheep(Agent):
+        pass
+
+    model = Model()
+    wolf = Wolf(1, model)
+    sheep = Sheep(2, model)
+
+    assert model.agents_by_type[Wolf] == AgentSet([wolf], model)
+    assert model.agents_by_type[Sheep] == AgentSet([sheep], model)
+    assert len(model.agents_by_type) == 2
diff --git a/tests/test_time.py b/tests/test_time.py
index a9d8e2e6..4203ca5d 100644
--- a/tests/test_time.py
+++ b/tests/test_time.py
@@ -320,7 +320,7 @@ class TestRandomActivationByType(TestCase):
         agent_types = model.agent_types
         for agent_type in agent_types:
             assert model.schedule.get_type_count(agent_type) == len(
-                model.get_agents_of_type(agent_type)
+                model.agents_by_type[agent_type]
             )
 
     # def test_add_non_unique_ids(self):