-
Notifications
You must be signed in to change notification settings - Fork 874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DiscreteEventScheduler #1890
Conversation
This commit introduces the DiscreteEventScheduler, an enhancement to the Mesa time module. This new scheduler is designed for discrete event simulation, where events are scheduled to occur at specific simulation times, rather than at regular, step-wise intervals. Key Features: - The scheduler advances the simulation to the time of the next scheduled event, handling multiple events efficiently. - If multiple events are scheduled for the same time, their execution order is randomized. This is achieved by adding a secondary sorting criterion based on a random value. This ensures fairness and reduces bias that might occur from consistent ordering. Modifications: - Added the DiscreteEventScheduler class with methods to schedule and process events. - Modified the `schedule_event` method to include a random value for secondary sorting in the event queue. - Adjusted the `step` method to handle events within a given time frame, advancing the simulation in fixed time steps.
Utility function to schedule an event a certain time from now.
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1890 +/- ##
==========================================
+ Coverage 77.10% 77.35% +0.25%
==========================================
Files 15 15
Lines 974 1007 +33
Branches 214 220 +6
==========================================
+ Hits 751 779 +28
- Misses 195 197 +2
- Partials 28 31 +3 ☔ View full report in Codecov by Sentry. |
…ndtime - Add type hints - Check for negative values when scheduling (you can't schedule backwards in time (but you can on the current time). - Include the end time in the step (change from < to <=). This enables you to schedule something at time = 1, then do one step of time 1, and have that step executed. Feels intuative.
Add a schedule_now keyword argument to the add() method of the DiscreteEventScheduler. It defaults to True, and if True, it will schedule the first event for the added agent immediately. Also add a Usage section to the docs.
Add a bunch of tests for the DiscreteEventScheduler
I don't know how to fix the last one mesa\mesa\time.py:389:13: S311 Standard pseudo-random generators are not suitable for cryptographic purposes
Implemented the last stuff, added tests, fixed two ruff errors (the last one I don't understand). This PR is ready for review. I'm quite happy with it, very curious what everybody thinks! |
How do you implement Poisson activation with this? |
You're probably talking about an arrival process right? Interesting question, could you expand a bit on what you're exactly thinking about? |
Right, I thought about it a bit and I think I understand what you mean (please expand a bit next time. PEP 20, explicit is better than implicit). We are thinking at this from two different ways:
So currently, the model_step uses a fixed time step. I build it that way to keep it consistent with the other schedulers (you can still do Basically I made the (implicit) design decision: Agents are scheduled with discrete events, but the model is not. However, if you want to centrally schedule events based on some distribution, like drawing from the Poisson distribution to simulate stochastic arrival times, the fixed time_step of the model doesn't allow that (currently). If we are going to modify this, the most logical option is for the model to step at each event. But then what does a model step mean, since it's the same as an agent step. I can see a few options:
I tried a few quick and dirty implementations, but each option with dropping the fixed model time step looks more complicated an note really like a Mesa scheduler anymore. Therefore, this also becomes a bit of a scoping decision. I think we need input from @jackiekazil, @tpike3 and @Corvince for that. To what extend do we want to support discrete-event simulation, and at what costs? |
As for the second option, letting arriving agent schedule the next one, we could add a def schedule_poisson(self, rate: float, agent: Agent) -> None:
""" Schedule an event based on Poisson process. """
if rate <= 0:
raise ValueError("Rate must be positive")
delay = random.expovariate(rate)
self.schedule_in(delay, agent) Then, an arrival process could look something like this: class CustomerAgent(Agent):
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
def step(self):
# Implement customer's behavior here
# Example: print("Customer", self.unique_id, "is being served.")
# Schedule the next customer's arrival
next_customer = CustomerAgent(self.unique_id + 1, self.model)
self.model.schedule.add(next_customer)
self.model.schedule.schedule_poisson(self.model.lambda_arrival, next_customer)
class ServiceCenterModel(Model):
def __init__(self, lambda_arrival):
self.schedule = DiscreteEventScheduler(self)
self.lambda_arrival = lambda_arrival
self.next_customer_id = 0
# Schedule the first customer
first_customer = CustomerAgent(self.next_customer_id, self)
self.schedule.add(first_customer)
self.schedule.schedule_poisson(self.lambda_arrival, first_customer)
self.next_customer_id += 1
def step(self):
self.schedule.step()
# Example usage
lambda_arrival = 0.5 # Average rate of customer arrival
model = ServiceCenterModel(lambda_arrival)
for _ in range(10): # Simulate 10 time steps
model.step() |
using self.model.random.random() allows seeding and thus reproduceable runs.
Ruff really is a bitch. I liked just Black better.
Wow! This is great thanks @EwoutH! Regarding the Poisson activation, I don't think it is necessary for this class. Although for a different I am fine with merging....thanks! |
Thanks!
I think adding the What do you think? (also @rht) What do you think would be useful for documentation. I attempted to get the docstring as clear as possible, but it might be nice to have a small tutorial or example model somewhere. What do you think would be most useful? |
Had a nice long bike ride, and thought about it some. In the core, Mesa is an agent-based modelling library. Agent-based modelling is built from the postulate that relatively simple behavior of many individual entities can lead to complex, emergent behavior. In this postulate, the environment is mostly just a shared state, with some dimensions like space and time, though which agent communicate. The agent is active, while the model is passive. The current In central discrete-event scheduler, events are mostly driven by a shared entity or model. So flips the principle around: The model decides what's happening to the agents, the model is active while the agents are passive. So while that's extremely interesting, it start to become another domain. Therefore, I would say that the current implementation is very much in line with what Mesa tries to attempt, and other extension might not be. The scheduler might need a different name though. |
One potential issue could be that at some point multiple events are (unintended) added to the stack for one agent, in which case it might do too many steps. I tried some mechanisms to circumvent this, but it isn't easy to get events for specific agents out of the As long as you schedule one event for each agent from the model, and then just let the agent itself schedule it's next event when it's done, it works great. So this thing is far from perfect and far from done, but I think it's a good start, and can be reasonable well extended with future desired behavior. I am mostly thinking about tasks interruptions and system-level events. I've made an issue there at #1893. |
This implementation uses a priority queue (heapq) to manage events. Each | ||
event is a tuple of the form (time, random_value, agent), where: | ||
- time (float): The scheduled time for the event. | ||
- random_value (float): A secondary sorting criterion to randomize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would have been clearer to say that the random_value
is used only once for the heapq.push
operation. I had to check elsewhere in the code for its usage, until I realized of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read it for again, I think it’s quite clear, especially together with the comments lower in the code..
Implementation LGTM. Waiting for agreement from the others for the merge. The Poisson activation can be showcased in an example model or in the how-to guide. |
Thanks! Tom already approved right? Anyone else you would like agreement from? (if so, maybe tag them) |
I think 2 is sufficient, so I will merge. Thanks! If you could also put the poisson activation into a how to I think that would be very useful |
Awesome, thanks for merging! Will work on a How To entry. By the way, I saw we're converting some docs from rst to md, is that also the plan for the howto.rst file? |
# Summary This PR adds an experiment feature that puts event scheduling at the heart of how MESA works. An earlier draft of this code was shown and discussed in #2032. This code generalizes #1890 by making discrete event scheduling central to how mesa models are run. This makes it trivial to maintain discrete time progression while allowing for event scheduling, thus making it possible to build hybrid ABM-DEVS models. # Motive Agent-based models can be quite slow because, typically, they involve the activation of all agents at each tick. However, this can become very expensive if it can be known upfront that the agent is not active. Combining ABM tick-based activation with event scheduling makes it easy to avoid activating dormant agents. For example, in Epstein's civil violence model, agents who are in jail need not be activated until they are released from jail. This release from jail can be scheduled as an event, thus avoiding unnecessary agent activations. Likewise, in Wolf-Sheep with grass, the regrowth of grass patches can be scheduled instead of activating all patches for each tick only to decrement a counter. # Implementation The experimental feature adds three core new classes: Simulator, EventList, and SimulationEvent. The simulator is analogous to a numerical solver for ODEs. Theoretically, the idea of a Simulator is rooted in the work of [Zeigler](https://www.sciencedirect.com/book/9780128133705/theory-of-modeling-and-simulation), The Simulator is responsible for controlling and advancing time. The EventList is a heapq sorted list of SimulationEvents. SimulationEvents are sorted based on their time of execution, their priority, and their unique_id. A SimulationEvent is, in essence, a callable that is to be executed at a particular simulation time instant. This PR adds two specific simulators: ABMSimulator and DEVSSimulator. ABMSimulator uses integers as the base unit of time and automatically ensures that `model.step` is scheduled for each tick. DEVSSimulator uses float as the base unit of time. It allows for full discrete event scheduling. Using these new classes requires a minor modification to a Model instance. It needs a simulator attribute to be able to schedule events. # Usage The basic usage is straightforward as shown below. We instantiate an ABMSimulator, instantiate the model, and call `simulator.setup`. Next, we can run the model for, e.g., 100 time steps). ```python simulator = ABMSimulator() model = WolfSheep(simulator,25, 25, 60, 40, 0.2, 0.1, 20, seed=15,) simulator.setup(model) simulator.run(100) print(model.time) # prints 100 simulator.run(50) print(model.time) # prints 150 ``` The simulator comes with a whole range of methods for scheduling events: `schedule_event_now`, `schedule_event_relative`, `schedule_event_absolute`, and the ABMSimulator also has a `schedule_event_next_tick`. See `experimental/devs/examples/*.*` for more details on how to use these methods.
# Summary This PR adds an experiment feature that puts event scheduling at the heart of how MESA works. An earlier draft of this code was shown and discussed in projectmesa#2032. This code generalizes projectmesa#1890 by making discrete event scheduling central to how mesa models are run. This makes it trivial to maintain discrete time progression while allowing for event scheduling, thus making it possible to build hybrid ABM-DEVS models. # Motive Agent-based models can be quite slow because, typically, they involve the activation of all agents at each tick. However, this can become very expensive if it can be known upfront that the agent is not active. Combining ABM tick-based activation with event scheduling makes it easy to avoid activating dormant agents. For example, in Epstein's civil violence model, agents who are in jail need not be activated until they are released from jail. This release from jail can be scheduled as an event, thus avoiding unnecessary agent activations. Likewise, in Wolf-Sheep with grass, the regrowth of grass patches can be scheduled instead of activating all patches for each tick only to decrement a counter. # Implementation The experimental feature adds three core new classes: Simulator, EventList, and SimulationEvent. The simulator is analogous to a numerical solver for ODEs. Theoretically, the idea of a Simulator is rooted in the work of [Zeigler](https://www.sciencedirect.com/book/9780128133705/theory-of-modeling-and-simulation), The Simulator is responsible for controlling and advancing time. The EventList is a heapq sorted list of SimulationEvents. SimulationEvents are sorted based on their time of execution, their priority, and their unique_id. A SimulationEvent is, in essence, a callable that is to be executed at a particular simulation time instant. This PR adds two specific simulators: ABMSimulator and DEVSSimulator. ABMSimulator uses integers as the base unit of time and automatically ensures that `model.step` is scheduled for each tick. DEVSSimulator uses float as the base unit of time. It allows for full discrete event scheduling. Using these new classes requires a minor modification to a Model instance. It needs a simulator attribute to be able to schedule events. # Usage The basic usage is straightforward as shown below. We instantiate an ABMSimulator, instantiate the model, and call `simulator.setup`. Next, we can run the model for, e.g., 100 time steps). ```python simulator = ABMSimulator() model = WolfSheep(simulator,25, 25, 60, 40, 0.2, 0.1, 20, seed=15,) simulator.setup(model) simulator.run(100) print(model.time) # prints 100 simulator.run(50) print(model.time) # prints 150 ``` The simulator comes with a whole range of methods for scheduling events: `schedule_event_now`, `schedule_event_relative`, `schedule_event_absolute`, and the ABMSimulator also has a `schedule_event_next_tick`. See `experimental/devs/examples/*.*` for more details on how to use these methods.
This PR introduces the DiscreteEventScheduler, an addition to the Mesa time module. This new scheduler is designed for discrete event simulation, where agent steps are scheduled to occur at specific simulation times, rather than at regular, step-wise intervals.
Key Features:
model.step()
works the same by default: It advances time by 1. The DiscreteEventScheduler has a keyword argumenttime_step
(default 1) which is the period in which the model is advanced by each model.step() call. In that period, agent may execute their step function zero, one or multiple times, depending on the amount of eventa scheduled for that agent.Modifications:
schedule_event
method to include a random value for secondary sorting in the event queue.step
method to handle events within a given time frame, advancing the simulation in fixed time steps.add
method to have aschedule_now
keyword, which is True by default. When True, the first event for that agent is immediately added to the schedule.Two example models are available, a very simple one and a slightly more complex one.
More complex example:
Both are also available in this ZIP, and below: discrete_event_example_models.zip
Todo:
Ready for review!