MiniEvents.jl allows for the easy definition and efficient execution of continuous-time, discrete-events agent-based models (ABMs). Usually ABMs are defined in terms of discrete time steps, where each agent performs a number of actions at every time step. For efficiency and epistemological reasons it can be preferable, however, to define the model as a number of events that occur stochastically in continuous time at given rates.
Using MiniEvents such a model can look like this:
struct World
pop :: Vector{Person}
end
@kwdef struct Params
r_inf :: Float64 = 1e-6
r_rec :: Float64 = 1e-2
t_inf :: Float64 = 1.0
end
# events that can apply to a Person
@events person::Person begin
# this is optional, it checks if activated objects have changed state since the last activation
@debug
@rate(count(p->p.status == infected, person.contacts) * @sim().pars.r_inf) ~
# this is a boolean condition that determines whether the event can take place
person.status == susceptible =>
begin
infect!(person)
# all objects whose event rates are affected need to be refreshed
@r person, person.contacts
end
@rate(@sim().pars.r_rec) ~
person.status == infected =>
begin
heal!(person)
@r person person.contacts
end
end
# events that can apply to the World
@events world::World begin
@debug
# events with a fixed time are supported as well
@repeat(@sim().pars.t_inf) =>
begin
p = rand(world.pop)
p.status = infected
@r p
end
end
# @simulation ties everything together
@simulation Model Person World begin
# we can add custom properties
world :: World
pars :: Params
end
function MiniEvents.spawn!(model::Model)
# before an object can receive events it needs to be activated
# spawn_pop! activates a list of objects at once
spawn_pop!(model.world.pop, model)
spawn!(model.world, model)
end
function step!(model)
# find and execute the next event in line
next_event!(model)
end
MiniEvents implements the direct method of the Gillespie algorithm and avoids memory allocations and dynamic function calls. Execution speed depends on model size (due to memory access) and model complexity, but even with a quarter of a million agents, hundreds of thousands of events per second can be processed (time for 500k events show):
.6086.0
grid size: 2
48.109 ms (0 allocations: 0 bytes)
grid size: 4
72.159 ms (0 allocations: 0 bytes)
grid size: 8
95.049 ms (0 allocations: 0 bytes)
grid size: 16
114.925 ms (0 allocations: 0 bytes)
grid size: 32
141.664 ms (0 allocations: 0 bytes)
grid size: 64
175.284 ms (0 allocations: 0 bytes)
grid size: 128
228.154 ms (0 allocations: 0 bytes)
grid size: 256
327.094 ms (0 allocations: 0 bytes)
grid size: 512
415.060 ms (0 allocations: 0 bytes)