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