Thinking about Time in the Simulation
The Simulation Clock
The SimulationClock plugin manages
the progression of time throughout the simulation. Fundamentally, that means it
keeps track of the current time (beginning at the start time), provides a mechanism
to advance the simulation time by some duration (the step size), and determines
when the simulation is complete via a configured end time. The simplest implementation
of a Clock is the SimpleClock object,
which is little more than an integer counter that is incremented by a fixed step
size until it reaches the end time. Modeling real-world events is often dependent
on data that are tied to particular years or rates, where the desired step size
is not necessarily known in advance. Therefore, it is common to use the
DateTimeClock, which uses datetime-like
objects (specifically Timestamp and Timedelta)
as the temporal units. The DateTimeClock can more easily facilitate the conversion
rates to particular increments of time.
Event Times
Discrete time simulations assume that all changes to a simulant’s state vector happen
at the end of the time step, that is, the current clock time plus the step size.
vivarium explicates this important distinction and labels this quantity the
event time. Events <events_concept> that correspond to (potential) state changes
are mediated through the Event Manager,
which propagates events to components subscribed to
them during particuar phases of the simulation lifecycle. The Event Manager uses
the event time when calculating time-related outcomes, for example, age- or year-dependent
rates of morbidity and mortality.
Time Interface
The Time plugin provides, via the Builder, an
interface to access several clock
methods that might be needed by other managers or components. In particular, components
can access the current time and step size (and, implicitly, the event time).
Individual Clocks
vivarium also allows one to update simulants asynchronously with different
frequencies depending on their state information. For example, a component that
simulates the progression of a disease might need to update the state of each simulant
more frequently when infected than when in remission. The basic method is to give
each simulant its own distinct clock time and step size instead of one global clock.
A simulant’s next event time, that is, the sum of its clock time and step size,
is when it is scheduled to be updated. Currently, vivarium still incorporates
a global clock, which determines the start, end, and minimal step size of the simulation.
The minimum step size is the smallest value that a simulant’s step size can take,
and therefore determines the minimum duration by which the simulation can advance
in a single iteration. However, global step size changes from iteration to iteration
and can be larger than the minimum step size. In each iteration of the simulation,
the global clock is advanced to the earliest time in which some simulant is scheduled
to be updated. Simulants that are not scheduled to be updated in a particular iteration
are simply excluded from the relevant events as propagated by the Event Manager.
In effect, if there are no simulants to be updated in a duration comprising several
minimum timesteps, those “minimum timesteps” are skipped.
The Time Interface provides a method to modify a simulant’s step size based on some
criteria, builder.time.register_step_size_modifier().
If there are multiple modifiers to the same simulant simultaneously, the time manager
chooses the smallest one (bounded by the global minimum step size). If a simulant
has no step modifier, it is given a default value, either the global minimum or
another optionally configurable value, the standard step size, in the case that
we want the “background” update frequency to be larger than the minimium size. If
no simulants have a step modifier, then the simulation behaves as if there were
no individual clocks, reverting to the global clock.