Refactor step scheduling to use EventGenerator internally#3260
Refactor step scheduling to use EventGenerator internally#3260
EventGenerator internally#3260Conversation
Replace manual SimulationEvent scheduling with an EventGenerator-based default schedule. The model now stores the user's step as _user_step and installs a default_schedule EventGenerator (interval 1.0, start 1.0, high priority) that calls _do_step and is started during init. Removed the old _schedule_step helper and the SimulationEvent-based scheduling; _do_step no longer reschedules itself (rescheduling is handled by the EventGenerator). Updated imports to include EventGenerator and Schedule.
Remove the ABMSimulator explicit initial step scheduling because the model's default_schedule is started in Model.__init__. In DEVSimulator.setup explicitly stop the model.default_schedule and clear any pre-scheduled events to avoid automatic step scheduling for pure DEVS models.
Turn default_schedule into a managed property: assigning a new EventGenerator stops any existing one and auto-starts the new one, and assigning None disables the schedule. Remove the immediate .start() call from the constructor so the setter handles lifecycle. Add a docstring clarifying behavior.
|
Thanks for this PR. I agree with the basic premise of using EvenGenerator for this, but I am not sure about the current implementation. I'll try to take a closer look asap.
With
I don't really like this I prefer opt-in over opt-out.
I agree wtih you on this completely and decoupling the two is the way to go. Time then becomes the purview of the evenlists and the run control. And this is also why I wanted time to be reactive. |
|
Performance benchmarks:
|
I think we almost go to a philosophical level of "are ticks core to ABM or were they just the easy implementation and everybody stuck with it". Anyway, we can't change that in Mesa 3.x, but it's something to discuss for Mesa 4. In that case it just will be as easy as setting But discussing the merrit of a |
As indicated, I am not convinced by it, but without diving into the code I am also not sure how you could otherwise achieve the things that are clearly valuable here. Namely, replacing "the internal hand-rolled step rescheduling mechanism with an exposed What I would like to see is an easy way for the user to specify that e.g. |
|
If we do see value in this refactor but not in exposing it (currently) to the public, we could of course make @mesa/maintainers I would like some more opinions and perspectives if we need a default scheduled method ("heart beat") or not. You don't need to dive in to the code, just conceptually if you see use/need for it. See also: #2921 (comment). |
| self.default_schedule = EventGenerator( | ||
| self, | ||
| self._do_step, | ||
| Schedule(interval=1.0, start=1.0), |
There was a problem hiding this comment.
why start at 1 instead of at zero? zero is just the state of the system prior to any events I guess (so the initial condition)?
There was a problem hiding this comment.
Basically because it’s the current convention in Mesa. 0 is setup, 1 the first step. Original discussion: #2229 (comment)
(thank our excellent migration guide to help me find this back).
There was a problem hiding this comment.
I recall that discussion, so yes this makes perfect sense.
I quick semi related question: do we allow Schedule(start=0)? Or should start always be larger than 0?
There was a problem hiding this comment.
In my view we explicitly allow that. The default is just to start after one interval, but that can be any non-negative number.
|
I reviewed the code. I don't have major issues with this. I think it is helpful to separate mesa 3 from mesa 4 going forward. We currently have a lot of baggage with Also, coverage is a bit low, so I assume you'll update some tests after which I am fine with approving this. |
Create an internal _default_schedule EventGenerator (and store the original user step in _user_step), start it immediately, and assign the wrapped step to self.step. Remove the public default_schedule property and its getter/setter to simplify lifecycle management. Update DEVSimulator to stop model._default_schedule instead of model.default_schedule. This avoids accidental recursion/misuse and ensures the recurring step event is started and stopped reliably.
default_schedule EventGeneratorEventGenerator internally
|
I realized this PR was doing two things: An internal refactor and exposing a new user API surface. That doesn't make it atomic, and so, let's split the two. I refactored this PR to only do the former. Then we can later discuss the latter. Since we don't expose a new API or enable new behavior, no additional tests are needed. |
I don't agree with this claim. The relevant question is how the coverage has changed due to refactoring and ensuring that the refactored code is still properly covered. That is the case here judged by codecov, but it also makes me wonder how the percentage is so low. I guess because the number of lines changed is quite small. |
|
Fair point. I'm planning on fully revising the test suite once we have done the post branching to Mesa 4 cleanup. |
See the "Does Mesa need a "heartbeat"?" discussion in #2921 (comment).
Summary
Replaces the hand-rolled step rescheduling loop in
Modelwith an internalEventGenerator, simplifying the code while keeping all behavior and API identical.Motive
The current step scheduling uses a manual pattern:
_do_step()calls_schedule_step(), which creates aSimulationEventthat calls_do_step()again. This is functionally anEventGenerator— but reimplemented ad-hoc. Now thatEventGeneratorexists (#3201), we should use it.This also removes
_schedule_step()entirely and simplifies both_do_step()and_wrapped_step(), reducing the amount of scheduling logic inModel.Implementation
The self-rescheduling loop (
_do_step()→_schedule_step()→SimulationEvent→_do_step()) is replaced by a single_default_scheduleEventGeneratorcreated and started inModel.__init__(). This simplifies_wrapped_step()to justself._advance_time(self.time + 1), reduces_do_step()to incrementing steps and calling the user's step method, and removes_schedule_step()entirely.On the simulator side,
Simulator.setup()now checksmodel._simulator is not Noneinstead ofevent_list.is_empty()(since the event list has events from_default_scheduleat init),ABMSimulator.setup()no longer needs to manually schedule the first step, andDEVSSimulator.setup()explicitly stops_default_schedulebefore clearing the event list.Additional Notes
model.step(),run_for(),run_until()behavior is unchanged_default_scheduleis private (_-prefixed). Exposing it as a public API with a property setter is left for a follow-up PRSimulator.setup()guard change from checkingevent_list.is_empty()tomodel._simulator is not Noneis necessary because the event list now contains events from_default_scheduleimmediately after init