.. _lifecycle_concept: ======================== The Simulation Lifecycle ======================== The life cycle of a :mod:`vivarium.engine` 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. .. list-table:: **Life Cycle Phases** :widths: 15 65 :header-rows: 1 * - Phase - Description * - | :ref:`Bootstrap ` - | Arguments passed from the simulation | :ref:`entry point ` are parsed and core | framework systems are initialized. * - | :ref:`Initialization ` - | Simulation managers and :ref:`components ` are | initialized and all :ref:`configuration ` | information is loaded. * - | :ref:`Setup ` - | Components register themselves with simulation services and | request access to resources by interacting with the simulation | :ref:`builder `; the initial population is created. * - | :ref:`Main Loop ` - | The core logic (as encoded in the simulation components) is executed. * - | :ref:`Simulation End ` - | 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 :mod:`~vivarium.engine.framework.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. .. contents:: :depth: 2 :local: :backlinks: none .. _lifecycle_bootstrap: 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 :term:`plugins `, :term:`components `, and :term:`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. .. _lifecycle_initialization: The Initialization Phase ------------------------ The initialization phase of a :mod:`vivarium.engine` simulation starts when the :class:`~vivarium.engine.framework.lifecycle.entities.LifeCycle` is fully constructed and ends when the ``__init__`` method of the :class:`vivarium.engine.framework.engine.SimulationContext` completes. Two important things happen here: - The internal representation of the simulation :term:`components ` is parsed into python import paths and **all** components are instantiated and registered with the component manager. - The internal representation of the :term:`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. .. _lifecycle_setup: 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 :ref:`builder ` as an argument. The builder allows the components to request services like :ref:`randomness ` or views into the :term:`state table ` or to register themselves with various simulation subsystems. Setting up components may also involve loading data, registering or getting :ref:`pipelines `, creating :ref:`lookup tables `, and registering :ref:`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 :class:`vivarium.engine.framework.engine.Builder` object. Post-setup ++++++++++ This is a short state that exists in the simulation mainly so that framework :term:`managers ` can coordinate shared state and do any necessary cleanup. This is the first actual :ref:`event ` emitted by the simulation framework. Normal ``vivarium`` :term:`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 :ref:`population ` for the simulation. Here, the framework rewinds the simulation :ref:`clock ` one time step and generates the population. This time step fence-posting ensures that :term:`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. .. _lifecycle_main_loop: 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 :ref:`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 :ref:`time step `: 1. *time_step__prepare* A state in which simulation :term:`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 :ref:`population 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 :class:`vivarium.engine.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. .. _lifecycle_simulation_end: 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* :ref:`event ` is emitted to signal that the event loop has finished and the :ref:`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.