Skip to content

Commit

Permalink
Merge branch 'main' into neighborhood
Browse files Browse the repository at this point in the history
  • Loading branch information
quaquel authored Sep 21, 2024
2 parents 4fb59dd + 25925a2 commit c754200
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 324 deletions.
47 changes: 47 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
---
title: Release History
---
# 3.0.0a5 (2024-09-21)
## Highlights
Mesa v3.0 alpha 5 release contains many quality of life updates, a big new feature for the DataCollector and a major deprecation.

The entire `mesa.time` module, including all schedulers, has been deprecated ([#2306](https://github.com/projectmesa/mesa/pull/2306)). Users are encouraged to transition to AgentSet functionality for more flexible and explicit agent activation patterns. Check the [migration guide](https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers) on how to upgrade.

The DataCollector now supports collecting data from specific Agent subclasses using the new `agenttype_reporters` parameter ([#2300](https://github.com/projectmesa/mesa/pull/2300)). This allows collecting different metrics for different agent types. For example:

```python
self.datacollector = DataCollector(
agenttype_reporters={
Wolf: {"sheep_eaten": "sheep_eaten"},
Sheep: {"wool": "wool_amount"}
}
)
```

Furthermore, a new `shuffle_do()` method for AgentSets provides a faster way to perform `shuffle().do()` ([#2283](https://github.com/projectmesa/mesa/pull/2283)). The GroupBy class gained `count()` and `agg()` methods to count the number of agents in groups and aggregate variables of them ([#2290](https://github.com/projectmesa/mesa/pull/2290)).
<!--- TODO: Add #2307, #2308, #2309 --->

Finally, SolaraViz received updates improving its interface and performance ([#2299](https://github.com/projectmesa/mesa/pull/2299), [#2304](https://github.com/projectmesa/mesa/pull/2304)). Cell connections in grids and networks are now public and named for more intuitive agent movements ([#2296](https://github.com/projectmesa/mesa/pull/2296)). The Model class initialization process was simplified by moving random seed and random object creation to `__init__` ([#1940](https://github.com/projectmesa/mesa/pull/1940)). Documentation has been extensively updated, including enforcing Google docstrings ([#2294](https://github.com/projectmesa/mesa/pull/2294)) and reorganizing the API documentation ([#2298](https://github.com/projectmesa/mesa/pull/2298)) for better clarity and navigation.

While the Mesa 3.0 timeline is still being discussed, we're aiming at the first Mesa 3.0 beta in October followed by a stable release in November. Testing new features and sharing feedback is appreciated!

## What's Changed
### 🎉 New features added
* GroupBy: Add `count` and `agg` methods by @EwoutH in https://github.com/projectmesa/mesa/pull/2290
* datacollector: Allow collecting data from Agent (sub)classes by @EwoutH in https://github.com/projectmesa/mesa/pull/2300
* Add optimized shuffle_do() method to AgentSet by @EwoutH in https://github.com/projectmesa/mesa/pull/2283
### 🛠 Enhancements made
* Make cell connections public and named by @Corvince in https://github.com/projectmesa/mesa/pull/2296
* SolaraViz Updates by @Corvince in https://github.com/projectmesa/mesa/pull/2299
* Solara viz: use_task for non-threaded continuous play by @Corvince in https://github.com/projectmesa/mesa/pull/2304
* Update to CellCollection.select by @quaquel in https://github.com/projectmesa/mesa/pull/2307
### 📜 Documentation improvements
* Enforce google docstrings by @quaquel in https://github.com/projectmesa/mesa/pull/2294
* Api docs by @quaquel in https://github.com/projectmesa/mesa/pull/2298
* update migration guide to describe solaraviz updates by @Corvince in https://github.com/projectmesa/mesa/pull/2297
* Migration Guide: Add Model initialization requirement and automatic Agent.unique_id assignment by @EwoutH in https://github.com/projectmesa/mesa/pull/2302
* Deprecate Time module and all its Schedulers by @EwoutH in https://github.com/projectmesa/mesa/pull/2306
### 🔧 Maintenance
* make typing behavior of AgentSet.get explicit by @quaquel in https://github.com/projectmesa/mesa/pull/2293
* model: Move random seed and random to __init__ by @rht in https://github.com/projectmesa/mesa/pull/1940
* Remove schedulers from benchmark models. by @quaquel in https://github.com/projectmesa/mesa/pull/2308

**Full Changelog**: https://github.com/projectmesa/mesa/compare/v3.0.0a4...v3.0.0a5

# 3.0.0a4 (2024-09-09)
## Highlights
Mesa 3.0.0a4 contains two major breaking changes:
Expand Down
1 change: 0 additions & 1 deletion benchmarks/BoltzmannWealth/boltzmann_wealth.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def __init__(self, seed=None, n=100, width=10, height=10):
super().__init__(seed)
self.num_agents = n
self.grid = mesa.space.MultiGrid(width, height, True)
self.schedule = mesa.time.RandomActivation(self)
self.datacollector = mesa.DataCollector(
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
)
Expand Down
4 changes: 1 addition & 3 deletions benchmarks/Flocking/flocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def __init__(
self.height = height
self.simulator = simulator

self.schedule = mesa.time.RandomActivation(self)
self.space = mesa.space.ContinuousSpace(self.width, self.height, True)
self.factors = {
"cohere": cohere,
Expand All @@ -138,11 +137,10 @@ def __init__(
**self.factors,
)
self.space.place_agent(boid, pos)
self.schedule.add(boid)

def step(self):
"""Run the model for one step."""
self.schedule.step()
self.agents.shuffle_do("step")


if __name__ == "__main__":
Expand Down
5 changes: 1 addition & 4 deletions benchmarks/Schelling/schelling.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from mesa import Model
from mesa.experimental.cell_space import CellAgent, OrthogonalMooreGrid
from mesa.time import RandomActivation


class SchellingAgent(CellAgent):
Expand Down Expand Up @@ -67,7 +66,6 @@ def __init__(
self.minority_pc = minority_pc
self.simulator = simulator

self.schedule = RandomActivation(self)
self.grid = OrthogonalMooreGrid(
[height, width],
torus=True,
Expand All @@ -84,12 +82,11 @@ def __init__(
agent_type = 1 if self.random.random() < self.minority_pc else 0
agent = SchellingAgent(self, agent_type, radius, homophily)
agent.move_to(cell)
self.schedule.add(agent)

def step(self):
"""Run one step of the model."""
self.happy = 0 # Reset counter of happy agents
self.schedule.step()
self.agents.shuffle_do("step")


if __name__ == "__main__":
Expand Down
99 changes: 98 additions & 1 deletion docs/migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,105 @@ You can access it by `Model.steps`, and it's internally in the datacollector, ba
#### Removal of `Model._advance_time()`
- The `Model._advance_time()` method is removed. This now happens automatically.

<!-- TODO deprecate all schedulers? -->
### Replacing Schedulers with AgentSet functionality
The whole Time module in Mesa is deprecated, and all schedulers are being replaced with AgentSet functionality and the internal `Model.steps` counter. This allows much more flexibility in how to activate Agents and makes it explicit what's done exactly.

Here's how to replace each scheduler:

#### BaseScheduler
Replace:
```python
self.schedule = BaseScheduler(self)
self.schedule.step()
```
With:
```python
self.agents.do("step")
```

#### RandomActivation
Replace:
```python
self.schedule = RandomActivation(self)
self.schedule.step()
```
With:
```python
self.agents.shuffle_do("step")
```

#### SimultaneousActivation
Replace:
```python
self.schedule = SimultaneousActivation(self)
self.schedule.step()
```
With:
```python
self.agents.do("step")
self.agents.do("advance")
```

#### StagedActivation
Replace:
```python
self.schedule = StagedActivation(self, ["stage1", "stage2", "stage3"])
self.schedule.step()
```
With:
```python
for stage in ["stage1", "stage2", "stage3"]:
self.agents.do(stage)
```

If you were using the `shuffle` and/or `shuffle_between_stages` options:
```python
stages = ["stage1", "stage2", "stage3"]
if shuffle:
self.random.shuffle(stages)
for stage in stages:
if shuffle_between_stages:
self.agents.shuffle_do(stage)
else:
self.agents.do(stage)
```

#### RandomActivationByType
Replace:
```python
self.schedule = RandomActivationByType(self)
self.schedule.step()
```
With:
```python
for agent_class in self.agent_types:
self.agents_by_type[agent_class].shuffle_do("step")
```

##### Replacing `step_type`
The `RandomActivationByType` scheduler had a `step_type` method that allowed stepping only agents of a specific type. To replicate this functionality using AgentSet:

Replace:
```python
self.schedule.step_type(AgentType)
```

With:
```python
self.agents_by_type[AgentType].shuffle_do("step")
```

#### General Notes

1. The `Model.steps` counter is now automatically incremented. You don't need to manage it manually.
2. If you were using `self.schedule.agents`, replace it with `self.agents`.
3. If you were using `self.schedule.get_agent_count()`, replace it with `len(self.agents)`.
4. If you were using `self.schedule.agents_by_type`, replace it with `self.agents_by_type`.
5. Instead of `self.schedule.add()` and `self.schedule.remove()`, agents are now automatically added to and removed from the model's AgentSet when they are created or removed.

From now on you're now not bound by 5 distinct schedulers, but can mix and match any combination of AgentSet methods (`do`, `shuffle`, `select`, etc.) to get the desired Agent activation.

Ref: Original discussion [#1912](https://github.com/projectmesa/mesa/discussions/1912), decision discussion [#2231](https://github.com/projectmesa/mesa/discussions/2231), example updates [#183](https://github.com/projectmesa/mesa-examples/pull/183) and [#201](https://github.com/projectmesa/mesa-examples/pull/201), PR [#2306](https://github.com/projectmesa/mesa/pull/2306)

### Visualisation

Expand Down
2 changes: 1 addition & 1 deletion mesa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
]

__title__ = "mesa"
__version__ = "3.0.0a4"
__version__ = "3.0.0a5"
__license__ = "Apache 2.0"
_this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
__copyright__ = f"Copyright {_this_year} Project Mesa Team"
4 changes: 2 additions & 2 deletions mesa/experimental/cell_space/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Cell:
def __init__(
self,
coordinate: Coordinate,
capacity: float | None = None,
capacity: int | None = None,
random: Random | None = None,
) -> None:
"""Initialise the cell.
Expand All @@ -66,7 +66,7 @@ def __init__(
self.agents: list[
Agent
] = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
self.capacity = capacity
self.capacity: int = capacity
self.properties: dict[Coordinate, object] = {}
self.random = random

Expand Down
33 changes: 22 additions & 11 deletions mesa/experimental/cell_space/cell_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,36 @@ def select_random_agent(self) -> CellAgent:
"""
return self.random.choice(list(self.agents))

def select(self, filter_func: Callable[[T], bool] | None = None, n=0):
def select(
self,
filter_func: Callable[[T], bool] | None = None,
at_most: int | float = float("inf"),
):
"""Select cells based on filter function.
Args:
filter_func: filter function
n: number of cells to select
at_most: The maximum amount of cells to select. Defaults to infinity.
- If an integer, at most the first number of matching cells is selected.
- If a float between 0 and 1, at most that fraction of original number of cells
Returns:
CellCollection
"""
# FIXME: n is not considered
if filter_func is None and n == 0:
if filter_func is None and at_most == float("inf"):
return self

return CellCollection(
{
cell: agents
for cell, agents in self._cells.items()
if filter_func is None or filter_func(cell)
}
)
if at_most <= 1.0 and isinstance(at_most, float):
at_most = int(len(self) * at_most) # Note that it rounds down (floor)

def cell_generator(filter_func, at_most):
count = 0
for cell in self:
if count >= at_most:
break
if not filter_func or filter_func(cell):
yield cell
count += 1

return CellCollection(cell_generator(filter_func, at_most))
13 changes: 13 additions & 0 deletions mesa/time.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Mesa Time Module.
.. warning::
The time module and all its Schedulers are deprecated and will be removed in a future version.
They can be replaced with AgentSet functionality. See the migration guide for details:
https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers
Objects for handling the time component of a model. In particular, this module
contains Schedulers, which handle agent activation. A Scheduler is an object
which controls when agents are called upon to act, and when.
Expand Down Expand Up @@ -57,6 +62,14 @@ def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None:
agents (Iterable[Agent], None, optional): An iterable of agents who are controlled by the schedule
"""
warnings.warn(
"The time module and all its Schedulers are deprecated and will be removed in a future version. "
"They can be replaced with AgentSet functionality. See the migration guide for details. "
"https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers",
DeprecationWarning,
stacklevel=2,
)

self.model = model
self.steps = 0
self.time: TimeT = 0
Expand Down
12 changes: 12 additions & 0 deletions tests/test_cell_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,15 @@ def test_cell_collection():

agents = collection[cells[0]]
assert agents == cells[0].agents

cell = collection.select(at_most=1)
assert len(cell) == 1

cells = collection.select(at_most=2)
assert len(cells) == 2

cells = collection.select(at_most=0.5)
assert len(cells) == 5

cells = collection.select()
assert len(cells) == len(collection)
Loading

0 comments on commit c754200

Please sign in to comment.