Discrete-event simulation
epydemic is basically a discrete-event simulator with a mixed
population of events.
The basics of discrete-event simulation
Discrete-event simulation represents a process as a sequence of events happening in sequence. Each event happens at a specific instant in time, and may give rise to further events to happen later. The job of the simulator is to progress simulated time forward and execute the events in the correct temporal order – even though they may be have been generated in some other order.
For example, suppose we have a sequence of events A, B, C, with A scheduled to happen before B, and B before C. The execution of A might given rise to two other events D and E where D occurs before B and E occurs after B but before C. After executing A, the future event order then becomes D, B, E, C, and the simulator will execute D next.
Clearly one cannot meaningfully create an event that should execute before the current simulation time.
Event implementation
Events are implemented in epydemic simply as methods on a
Process object that are called when the event “fires”. An
event is associated with a node or edge in the network, and can then
perform any arbitrary function relating to that network
element. (Examples include changing the compartments of nodes,
or re-wiring edges.)
Events can also be named, which both provides a useful hook for debugging and explanation, and is a key part of the Event taps sub-system.
Posted and stochastic events
epydemic supports two kinds of events: posted and stochastic.
A posted event is an event that happens at a specific, known,
simulation time. that is to say, both the event and the time when
it will happen are known when the event is created. Events are posted
by calling Dynamics:postEvent().
A stochastic event is an event chosen using a stochastic process,
meaning that the event happens (or doesn’t) on some model element
(typically a node or edge). Events are declared using
Dynamics.addFixedRateEventr() or Dynamics.addEventPerElement().
Exactly how these events are chosen depend on the dynamics used (see below).
Decomposition
Different parts of the code are responsible for different aspects of the simulation.
The Dynamics class abstracts the functions of the
simulator. It maintains an event queue containing all posted events,
the loci for stochastic events, and the event probabilities that apply
to those loci. Taken together, these form the distribution for
stochastic events.
Each dynamics class has an associated Process instance which
build the process in terms of loci and event probabilities, and
defines the event functions. The SIR process, for example,
has two events types (infection and removal). The probability of these
events occuring is a function of the number of SI edges and the number
of I nodes (two loci), and the disease dynamics specified when the
process is created.
The logic of this decomposition is that the Dynamics holds
all the information relating to a single simulation run. The
Process sets up the simulation and provides the logic for how
events are handled. This makes epydemic more flexible in two key
respects: it can support diffrent simulation dynamics using the same
process definition (see below), and it can combine processes together
to form composite processes. Neither of these two options would be
available if we (for example) defined a process by sub-classing the
simulation dynamics directly.
Event taps
As well as evaluating an event function, the event stream is “tapped”
to a single well-known method, NetworkExperiment.eventFired().
The event taps interface is implemented on NetworkExperiment
to allow all experiments to generate an event stream. This allows
sub-classes of NetworkExperiment to act on every event.
Event taps are most naturally associated with the Dynamics
sub-classes, all of which provide event taps in their main event loop
that tap all events regardless of which process defines it and
whether it was stochastic or posted. When events are defined or posted
using the various methods, they can optionally be given names. These
names are then passed to NetworkExperiment.eventFired(), and
form the only way in which different types of events can be
differentiated.
This feature isn’t used at all within “core” epydemic, but the
appropriate calls are in place for all the core classes (for example
StochasticDynamics and BondPercolation). It is
provided as another extension mechanism that can be used if and when
needed.