Add public event scheduling and time advancement methods#3266
Add public event scheduling and time advancement methods#3266
Conversation
|
Performance benchmarks:
|
| agent.remove() | ||
|
|
||
| ### Event scheduling and time progression methods ### | ||
| def schedule_event( |
There was a problem hiding this comment.
I can accept this design, although you know my preference to separate them into schedule_event_at and schedule_event_after.
There was a problem hiding this comment.
The coin fall to your liking with run_for and run_until ;)
On a bit more serious note, I went for this way because schedule_event(something, at=2.5, after=1) can make some conceptual sense (in this example firing two events), while run(for=1, untill=2.5) is by definition mutually exclusive (this regardless from if we want to enable it or not).
But I'm happy to follow the majority here.
There was a problem hiding this comment.
This is a tough one. I like schedule_event ever so slightly better, because it feels more self contained.
quaquel
left a comment
There was a problem hiding this comment.
API wise this looks good to me.
|
Found and fixed a subtle bug while writing these tests: Fix is two lines: |
|
@mesa/maintainers please critically review the API and functionality. This is an API we're going to use a lot! |
|
If no objections, I would like to merge this tomorrow and move forward. |
|
I like these updates. |
|
I will add one thing -- I think we need a more formal process for proposing such changes -- like a write up. |
|
@jackiekazil we do have a process: https://github.com/mesa/mesa/blob/main/CONTRIBUTING.md#mesa-development-process (#3135). In this case there was:
I think the current process was followed quite well. Of course, we could sharpen it. What do you find lacking or how would you improve it? |
Introduce event scheduling and time-progression APIs on Model. Adds schedule_event (one-off events with at/after validation), schedule_recurring (creates and starts an EventGenerator from a Schedule), run_for (advance by duration) and run_until (advance to absolute time). Update imports to include Callable and additional eventlist types (EventGenerator, Schedule, SimulationEvent, Priority). These helpers expose existing event-list functionality through a simpler public interface.
schedule_recurring() returns an EventGenerator whose _execute_and_reschedule method is wrapped in a WeakMethod by SimulationEvent. If the user doesn't save the returned generator (fire-and-forget), it gets garbage collected and the WeakMethod silently resolves to None — recurring events never fire. The fix: Model holds strong references to generators in _event_generators so they stay alive for the duration of the simulation.
Covers run_for, run_until, schedule_event, and schedule_recurring. Includes explicit GC test (test_fire_and_forget_survives_gc) to prevent regression on the WeakMethod lifetime bug.
Summary
Exposes event scheduling and time advancement methods directly on
Model, giving users a public API for scheduling events and advancing simulation time without needing to interact with theSimulatorclass.Basically, these methods allow us:
Motive
Currently, users advance their models by calling
model.step(), which is tightly coupled to the concept of a single discrete step. With the event-based architecture taking shape (#3201, #3204), we need public methods that let users think in terms of time rather than steps.The key method here is
model.run_for(duration). For existing ABM users,model.run_for(1)is functionally equivalent tomodel.step(): it advances time by 1 unit, which triggers the scheduled step event. But unlikestep(), it generalizes naturally:run_for(0.5)advances half a time unit,run_for(100)advances 100 units processing all scheduled events along the way. This makesrun_forthe foundation for both traditional ABMs and event-driven models.This is also a stepping stone toward deprecating
model.step()as the primary way to advance a model. Instead of calling a method that is a step, users call a method that advances time, and steps happen as scheduled events within that time. The mental model shifts from "execute step N" to "advance time, and whatever is scheduled will run."Implementation
Four new public methods on
Model:schedule_event(function, *, at, after, priority): schedule a one-off event at an absolute or relative timeschedule_recurring(function, schedule, priority): schedule a recurring event using aScheduleobject, returns anEventGeneratorrun_for(duration): advance time by the given duration, processing all eventsrun_until(end_time): advance time to an absolute point, processing all eventsAll four are thin wrappers around existing internal machinery (
_event_list,_advance_time,EventGenerator).Modelalso stores strong references to activeEventGenerators in_event_generatorsto prevent garbage collection —SimulationEventwraps bound methods inWeakMethod, so without this a fire-and-forgetschedule_recurring()call would silently stop working.Testing
Tests cover
run_for,run_until,schedule_event(at/after/cancel/validation), andschedule_recurring(fixed interval/stop/count). Includes an explicitgc.collect()regression test for theWeakMethodlifetime issue.Usage Examples
Drop-in replacement for
model.step():Schedule custom events alongside steps:
Run to a specific time:
Additional Notes
model.step()continues to work exactly as beforerun_for(1)andstep()produce identical results for standard ABMs, since both advance time by 1 unit and trigger the same scheduled step eventschedule_eventandschedule_recurringreturn their event/generator objects, allowing cancellation via.cancel()/.stop()SimulatorattachedOne we agree on the API I will add tests. In separate PRs we can update examples and tutorials and deprecate old API surfaces.