Execution of Dynamic Scenarios

As described in our tutorial on Dynamic Scenarios, Scenic scenarios can specify the behavior of agents over time, defining a policy which chooses actions for each agent at each time step. Having sampled an initial scene from a Scenic program (see Scene Generation), we can run a dynamic simulation by setting up the scene in a simulator and running the policy in parallel to control the agents. The API for running dynamic simulations is described in Using Scenic Programmatically (mainly the Simulator.simulate method); this page details how Scenic executes such simulations.

The policy for each agent is given by its dynamic behavior, which is a coroutine that usually executes like an ordinary function, but is suspended when it takes an action (using take or wait) and resumed after the simulation has advanced by one time step. As a result, behaviors effectively run in parallel with the simulation. Behaviors are also suspended when they invoke a sub-behavior using do, and are not resumed until the sub-behavior terminates.

When a behavior is first invoked, its preconditions are checked, and if any are not satisfied, the simulation is rejected, requiring a new simulation to be sampled. [1] The behavior’s invariants are handled similarly, except that they are also checked whenever the behavior is resumed (i.e. after taking an action and after a sub-behavior terminates).

Monitors and compose blocks of modular scenarios execute in the same way as behaviors, with compose blocks also including additional checks to see if any of their terminate when conditions have been met or their temporal requirements violated.

In detail, a single time step of a dynamic simulation is executed according to the following procedure:

  1. Execute all currently-running modular scenarios for one time step. Specifically, for each such scenario:

    1. Check if any of its temporal requirements have already been violated [2]; if so, reject the simulation.

    2. Check if the scenario’s time limit (if terminate after has been used) has been reached; if so, go to step (e) below to stop the scenario.

    3. If the scenario is not currently running a sub-scenario (with do), check its invariants; if any are violated, reject the simulation. [1]

    4. If the scenario has a compose block, run it for one time step (i.e. resume it until it or a subscenario it is currently running using do executes wait). If the block executes a require statement with a false condition, reject the simulation. If it executes terminate or terminate simulation, or finishes executing, go to step (e) below to stop the scenario.

    5. If the scenario is stopping for one of the reasons above, first recursively stop any sub-scenarios it is running, then revert the effects of any override statements it executed. Next, check if any of its temporal requirements were not satisfied: if so, reject the simulation. Otherwise, the scenario returns to its parent scenario if it was invoked using do; if it was the top-level scenario, or if it executed terminate simulation, we set a flag indicating the top-level scenario has terminated. (We do not terminate immediately since we still need to check monitors in the next step.)

  2. Save the values of all record statements, as well as record initial statements if it is time step 0.

  3. Run each monitor instantiated in the currently-running scenarios for one time step (i.e. resume it until it executes wait). If it executes a require statement with a false condition, reject the simulation. If it executes terminate, stop the scenario which instantiated it as in step (1e) above. If it executes terminate simulation, set the termination flag (and continue running any other monitors).

  4. If the termination flag is set, any of the terminate simulation when conditions are satisfied, or a time limit passed to Simulator.simulate has been reached, go to step (10) to terminate the simulation.

  5. Execute the dynamic behavior of each agent to select its action(s) for the time step. Specifically, for each agent’s behavior:

    1. If the behavior is not currently running a sub-behavior (with do), check its invariants; if any are violated, reject the simulation. [1]

    2. Resume the behavior until it (or a subbehavior it is currently running using do) executes take or wait. If the behavior executes a require statement with a false condition, reject the simulation. If it executes terminate, stop the scenario which defined the agent as in step (1e) above. If it executes terminate simulation, go to step (10) to terminate the simulation. Otherwise, save the (possibly empty) set of actions specified for the agent to take.

  6. For each agent, execute the actions (if any) its behavior chose in the previous step.

  7. Run the simulator for one time step.

  8. Increment the simulation clock (the currentTime attribute of Simulation).

  9. Update every dynamic property of every object to its current value in the simulator.

  10. If the simulation is stopping for one of the reasons above, first check if any of the temporal requirements of any remaining scenarios were not satisfied: if so, reject the simulation. Otherwise, save the values of any record final statements.

Footnotes