The Simulation Lifecycle

The life cycle of a vivarium simulation is a representation of the different execution states and their transitions. An execution state is a clearly delineated execution period during the simulation around which we build and enforce concrete programmatic contracts. These states can be grouped into five important phases. The phases are closely related groups of execution states. Contracts are not enforced directly around phases, but they are a useful tool for thinking about the execution flow during a simulation.

Life Cycle Phases

Phase

Description

Arguments passed from the simulation
entry point are parsed and core
framework systems are initialized.
Simulation managers and components are
initialized and all configuration
information is loaded.
Components register themselves with simulation services and
request access to resources by interacting with the simulation
builder; the initial population is created.
The core logic (as encoded in the simulation components) is executed.
The population state is finalized and results are tabulated and written
to disk.

The simulation itself maintains a formal representation of its internal execution state using the tools in the lifecycle module. The tools allow the simulation to make concrete contracts about flow of execution in simulations and to constrain the availability of certain framework services to particular life cycle states. This makes error handling much more robust and allows users to more easily reason about complex simulation models.

Todo

Make a graph of service availability in the simulation.

The Bootstrap Phase

The bootstrap and initialization phases look like an atomic operation to an external user. Bootstrap only exists as a separate phase because certain operations must take place before the internal representation of the simulation life cycle exists.

During bootstrap, all user input arguments are parsed into an internal representation of the simulation plugins, components, and configuration. The internal plugin representation is then parsed into the simulation managers, the set of private and public services used to build and run simulations. Finally, the formal representation of the simulation lifecycle is constructed and the initialization phase begins.

The Initialization Phase

The initialization phase of a vivarium simulation starts when the LifeCycle is fully constructed and ends when the __init__ method of the vivarium.framework.engine.SimulationContext completes.

Two important things happen here:

  • The internal representation of the simulation components is parsed into python import paths and all components are instantiated and registered with the component manager.

  • The internal representation of the configuration is updated with all component configuration defaults.

At this point, all input arguments have been parsed, all components have been instantiated and registered with the framework, and the configuration is effectively complete. In an interactive setting, this is a useful phase in the simulation life cycle because you can add locally created components and modify the configuration.

The Setup Phase

The setup phase is broken down into three life cycle states.

Setup

The first state is named the same as the phase and is where the bulk of the phases work is done. During the setup state, the simulation managers and then the simulation components will have their setup method called with the simulation builder as an argument. The builder allows the components to request services like randomness or views into the population state table or to register themselves with various simulation subsystems. Setting up components may also involve loading data, registering or getting pipelines, creating lookup tables, and registering population initializers, among other things. The specifics of this are determined by the setup method on each component - the framework itself simply calls that method with a vivarium.framework.engine.Builder object.

Post-setup

This is a short state that exists in the simulation mainly so that framework managers can coordinate shared state and do any necessary cleanup. This is the first actual event emitted by the simulation framework. Normal vivarium components should never listen for this event. This may be enforced at a later date.

Population Initialization

It’s not until this stage that the framework actually generates the base population for the simulation. Here, the framework rewinds the simulation clock one time step and generates the population. This time step fence-posting ensures that simulants enter the simulation on the correct start date. Note that this rewinding of the clock is purely what it sounds like - there is no concept of a time step being taken here. Instead, the clock is literally reset back the duration of one time step. Once the simulant population is generated, the clock is reset to the simulation start time, again by changing the clock time only without any time step being taken.

The Main Event Loop

At this stage, all the preparation work has been completed and the framework begins to move through the simulation. This occurs as an event loop. Like the the setup phase, the main loop phase is broken into a series of simulation states. The framework signals the state transitions by emitting a series of events for each time step:

  1. time_step__prepare A state in which simulation components can do any work necessary to prepare for the time step.

  2. time_step The phase in which the bulk of the simulation work is done. Simulation state is updated.

  3. time_step__cleanup A phase for simulation components to do any post time step cleanup.

  4. collect_metrics A life-cycle phase specifically reserved for computing and recording simulation outputs.

By listening for these events, individual components can perform actions, including manipulating the state table. This sequence of events is repeated until the simulation clock passes the simulation end time.

Note

We have multiple sources of time during this process. The vivarium.framework.engine.SimulationContext itself holds onto a clock. This simulation clock is the actual time in the simulation. Events (including e.g., time_step) come with a time as well. This time is the time at the start of the next time step, that is, the time when any changes made during the loop will happen.

The Simulation End Phase

The final phase in the simulation life cycle is fittingly enough, simulation end. It is split into two states. During the first, the simulation_end event is emitted to signal that the event loop has finished and the state table is final. At this point, final simulation outputs are safe to compute. The second state is report in which the simulation will accumulate all final outputs and write them to disk.