Skip to content

Conversation

@EwoutH
Copy link
Member

@EwoutH EwoutH commented Dec 5, 2025

Second implementation of #2921.

All existing tests pass!

This commit introduces a unified time and event scheduling API directly into the Model class, eliminating the need for separate Simulator objects.

The core insight is that users shouldn't need to manage two objects (Model and Simulator) to run a simulation. By integrating scheduling into Model, we get a cleaner API: model.run(until=100) instead of creating a simulator, calling setup(), then run_until().

Implementation uses composition: a new Scheduler class handles all event scheduling and execution logic, while Model provides thin delegation methods. This keeps Model focused on agents and state while Scheduler handles time.

Key changes:
- Add Scheduler class in mesa/experimental/devs/scheduler.py
- Add Model.schedule() for event scheduling (at= or after= syntax)
- Add Model.run() with multiple termination options
- Add Model.cancel() for canceling scheduled events
- Deprecate ABMSimulator/DEVSimulator (still functional with warnings)

Part of the unified time/event scheduling proposal (projectmesa#2921).
Fixes three issues:

1. The `_simulator` attribute was removed but tests still expect it
2. The `step()` method doesn't increment `steps` anymore when using the new event-driven approach
3. Tests expect the simulator to control time via `_simulator` attribute

## Issues
### 1. Missing `_simulator` attribute
The new implementation removed `model._simulator` but some tests still check for it. Since the Simulator classes are now deprecated wrappers, we don't need this attribute.

### 2. `step()` doesn't increment counters
In the new implementation, when a model has an overridden `step()` method without `@scheduled`, it wraps it to auto-increment. However, the base `Model.step()` is just a pass, so calling it directly doesn't do anything.

### 3. Time management changed
The new design uses the Scheduler internally, so there's no `_simulator` attribute to track.

## Fixes
The three failing tests were fixed by:

1. **`test_model_set_up` - Missing `_simulator` attribute**: Added `self._simulator = None` in `Model.__init__()` for backward compatibility with existing tests.
2. **`test_model_time_increment` - Steps counter not incrementing**: Modified `_setup_step_handling()` to wrap even the base `Model.step()` method in legacy mode. This ensures that calling `model.step()` directly (as tests do) still increments `steps` and `time`.
3. **`test_model_time_with_simulator` - Simulator not setting `_simulator`**: Modified `Simulator.setup()` to set `model._simulator = self` and `Simulator.reset()` to clear it with `model._simulator = None`.

The key insight is that the implementation needs to maintain backward compatibility with:
- Direct calls to `model.step()` (legacy behavior)
- Tests checking for `model._simulator`
- The Simulator API setting `_simulator`

All of this is done while still supporting the new `@scheduled` decorator and event-driven approaches for new code.
Not something we're going to support anymore
@github-actions
Copy link

github-actions bot commented Dec 5, 2025

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🟢 -4.5% [-5.4%, -3.5%] 🔵 -0.7% [-0.8%, -0.5%]
BoltzmannWealth large 🔵 +11.0% [-11.7%, +35.5%] 🔵 +4.7% [-0.9%, +13.5%]
Schelling small 🔵 -3.1% [-3.2%, -3.0%] 🔵 -0.2% [-0.4%, -0.1%]
Schelling large 🔵 -3.2% [-9.9%, +1.3%] 🔵 +10.5% [-5.5%, +36.4%]
WolfSheep small 🟢 -6.9% [-7.4%, -6.5%] 🔵 -4.5% [-8.8%, -0.4%]
WolfSheep large 🔵 +153.5% [-1.9%, +460.6%] 🟢 -18.8% [-35.9%, -3.2%]
BoidFlockers small 🟢 -7.9% [-8.3%, -7.5%] 🔵 -2.3% [-2.4%, -2.1%]
BoidFlockers large 🟢 -8.5% [-8.9%, -7.9%] 🔵 -1.5% [-1.7%, -1.3%]

@EwoutH EwoutH marked this pull request as draft December 5, 2025 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant