Skip to content
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

support for discrete event scheduling #2066

Merged
merged 50 commits into from
Apr 10, 2024
Merged

support for discrete event scheduling #2066

merged 50 commits into from
Apr 10, 2024

Conversation

quaquel
Copy link
Member

@quaquel quaquel commented Feb 29, 2024

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, 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).

    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.

@quaquel quaquel added the feature Release notes label label Feb 29, 2024
@quaquel quaquel added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Feb 29, 2024
@EwoutH
Copy link
Member

EwoutH commented Feb 29, 2024

This looks like an remarkable effort. I will try to review after the weekend.

@quaquel
Copy link
Member Author

quaquel commented Feb 29, 2024

The benchmarks are currently failing because they rely on a for loop. I'll have to update all benchmarks to use a simulator. Will probably do this tomorrow.

@Corvince
Copy link
Contributor

Corvince commented Mar 3, 2024

Just started to finally look into this, but already want to say great effort and awesome work! I'll have a few questions, but first impression is very good.

@quaquel quaquel added the trigger-benchmarks Special label that triggers the benchmarking CI label Mar 3, 2024
Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
Schelling small 🔵 +0.5% [+0.2%, +0.8%] 🔵 +0.0% [-0.2%, +0.2%]
Schelling large 🔵 +1.0% [+0.2%, +1.8%] 🔵 -0.9% [-2.2%, +0.8%]
WolfSheep small 🔵 -3.0% [-4.2%, -1.9%] 🟢 -60.1% [-60.8%, -59.5%]
WolfSheep large 🔵 -1.6% [-2.3%, -0.9%] 🟢 -54.7% [-55.5%, -53.8%]
BoidFlockers small 🟢 -87.2% [-87.3%, -87.2%] 🔵 -0.9% [-1.6%, -0.2%]
BoidFlockers large 🟢 -87.3% [-87.4%, -87.3%] 🔵 +0.3% [-0.2%, +0.9%]

@EwoutH
Copy link
Member

EwoutH commented Mar 24, 2024

These benchmarks ❤️!

Do you know why the BoidFlockers initialization is so much faster?

I will try to review tomorrow.

@quaquel
Copy link
Member Author

quaquel commented Mar 24, 2024

Do you know why the BoidFlockers initialization is so much faster?

This branch was not up to date with main, so something in main should explain that. My hunch is that it is #2083.

@tpike3
Copy link
Member

tpike3 commented Mar 27, 2024

This is amazing!

The only real block I really saw was the merge conflict so I resolved the merge conflict in flockers benchmark.

As Ewout, rht and Corvince have all reviewed this, I don't think I will provide any more value added.

I would ask one of them to do the actual merge if they are good with it and then per the dev session we can release 2.3.0.

@quaquel
Copy link
Member Author

quaquel commented Mar 27, 2024

I am meeting with @EwoutH in a bit. I'll discuss the PR and any possible open issues.

@EwoutH
Copy link
Member

EwoutH commented Apr 4, 2024

I think the only thing that needed to be done was to remove the old DiscreteEventScheduler and put a warning there, right?

@quaquel
Copy link
Member Author

quaquel commented Apr 5, 2024

That is the most important thing indeed. I might have some time over the weekend to finalize this.

@quaquel
Copy link
Member Author

quaquel commented Apr 7, 2024

I think the only thing that needed to be done was to remove the old DiscreteEventScheduler and put a warning there, right?

time.DiscreteEventScheduler is now largely removed and an Exception is raised if a user tries to instantiate it. I have also removed the associated tests and fixed some outstanding ruff issues. I think this is ready to be merged.

@EwoutH EwoutH merged commit 4cc03a4 into projectmesa:main Apr 10, 2024
11 of 12 checks passed
vitorfrois pushed a commit to vitorfrois/mesa that referenced this pull request Jul 15, 2024
# 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.
@EwoutH EwoutH mentioned this pull request Aug 5, 2024
@EwoutH
Copy link
Member

EwoutH commented Aug 5, 2024

@quaquel in my model I'm doing:

simulator1 = DEVSimulator()
model1 = UrbanModel(n_agents=5, simulator=simulator1)
simulator1.setup(model1)

In the initialisation the first events are scheduled. However, when I then do simulator1.setup(model1), to associate the model with the simulator, it clears the event queue:

self.event_list.clear()

Luckily this is quite easily circumvented by replacing the last line with simulator1.model = model1, instead of doing the setup.

However, I'm quite afraid most users won't discover this, since all the examples use the setup() method. So my concrete questions:

  • What is the rationale between clearing the event queue on simulator.setup()?
  • Should this clearing be identical between the ABMSimulator and DEVSimulator?
    (since the ABMSimulator does schedule an initial event, but the DEVSimulator doesn't)
  • How do we communicate this to users in our documentation and examples?

@EwoutH
Copy link
Member

EwoutH commented Aug 5, 2024

Another question: Is simulator time (model.simulator.time) kept in some way in sync with model time (model._time)? Or are your expected to only use simulator time when using a DEVSimulator()?

@quaquel
Copy link
Member Author

quaquel commented Aug 5, 2024

What is the rationale between clearing the event queue on simulator.setup()?

Running a model multiple times in a row. I also just followed dsol here.

Should this clearing be identical between the ABMSimulator and DEVSimulator?

Ideally yes, there should be a common way to reset a model to its initial state. This could be done on the model or the simulator. I need to think whether I have a preference.

How do we communicate this to users in our documentation and examples?

Let's first figure out what we want. I personally have in the passed pushed for a model.init and model.reset method. This would avoid the problem you ran into because any event scheduling would go to model.init. So you get something like.

simulator = DevsSimulator()
model = myModel()
simulator.setup(model)
model.init()
simulator.run()

Another question: Is simulator time (model.simulator.time) kept in some way in sync with model time (model._time)? Or are you expected to only use simulator time when using a DEVSimulator()?

I don't recall, so I would need to check the code. In my vision for MESA 3, time should be kept by the simulator, not by the model.

@EwoutH
Copy link
Member

EwoutH commented Aug 5, 2024

Thanks for the comments, they are useful and it's good to have this documented. I got it working as need for my model right now, but it also shows it's not directly ready to be stabilized. It requires a few more iterations of building models --> discovering limitations --> implementing feedback --> building models.

When you figure out how the internals work it's quite powerful though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Release notes label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants