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 the latter also including additional checks to see if any of their terminate when conditions have been met or their require always conditions 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 require always conditions are violated; if so, reject the simulation.

    2. Check which require eventually conditions are satisfied; remember these for later.

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

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

    5. 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 finishes executing, go to step (f) below to stop the scenario.

    6. If the scenario is stopping for one of the reasons above, first check if any of the require eventually conditions were never 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, 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 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, 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, 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. Update every dynamic property of every object to its current value in the simulator.

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

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

Footnotes

1(1,2,3)

By default, violations of preconditions and invariants cause the simulation to be rejected; however, Simulator.simulate has an option to treat them as fatal errors instead.