Direct API for Engines

The direct API works when your underlying engine has an API to directly control the dynamics. For example, there’s a Python function you can use to tell the engine to run for 10 time steps, and give you the result. The OPS direct engine API is then just a wrapper to translate between OPS and the underlying engine’s API.

Direct Engine API Overview

If your engine will use direct control, then your engine class should inherit from DynamicsEngine. More documentation for the methods discussed below can be found in the documentation for that class.

Engines take an options dictionary to provide details of how they run. All direct control engines need to be able to use the options:

  • n_steps_per_frame: Number of internal time steps per saved frame (snapshot).

  • n_frames_max: Maximum number of snapshots allowed in a trajectory before the simulation stops.

You will need to implement these methods:

  • current_snapshot (@property): Getter and setter for the current snapshot. We assume that the current state of the system (e.g., positions and velocities) exists inside your engine; this is the code for translating that internal representation to/from the OPS representation as a Snapshot.

  • generate_next_frame(): Takes the (previously set) current_snapshot and generates the next saved frame, i.e., it performs n_steps_per_frame individual time steps.

Optionally, you can override several other methods:

  • start(snapshot=None): Run before each trajectory.

  • stop(trajectory): Run after each trajectory ends.

  • generate_n_frames(n_frames=1): Generate a fixed number of frames at once; this can be used for performance in some cases. By default, this just calls generate_next_frame, but calls it n_frames times.

  • is_valid_snapshot(snapshot): Check whether the snapshot is physically valid (can be used to check types, look for NaNs, etc.)

Note that you are likely to also need to override the to_dict and from_dict methods; see the storage documentation for more details.

How the Direct Engine API Runs

This subsection gives an overview of what order the methods that make up the direct engine API are called in the course of a trajectory. A simulation can consist of multiple trajectories, so

  • Start of simulation: Code that needs to be run before performing any dynamics should be part of the standard __init__ of your engine subclass.

  • Start of dynamics: Before a specific dynamics run, we (1) set the snapshot with engine.current_snapshot = snapshot, using the current snapshot property’s setter; and (2) call engine.start().

  • During dynamics: During the dynamics, we call generate_next_frame (or possibly generate_n_frames), which will typically call the getter for engine.current_snapshot internally (to return the snapshot). The logic within OPS determines whether to ask for more frames from the engine.

  • After dynamics: When the OPS logic says that no more frames are required from the engine, we call engine.stop(trajectory), where trajectory is the trajectory we have created. This is a good place to do any per-trajectory clean-up that you need, but may simply be a no-op for many direct API engines.

Direct Engine API Step-by-Step

Or: “So you want to make a direct API engine?”

Direct API engines are generally straightforward.

  1. Start by determining what features your subclass of Snapshot will require. Define the snapshot class. See Snapshot Features for details.

  2. Create the class, and set up its intialization. You will need to:

    1. Add any options that you use should have some, with appropriate default values, in the _default_options class variable (a dictionary).

    2. Create a SnapshotDescriptor instance for your engine instance (and pass it to the DynamicsEngine initialization through super.)

  3. Write required methods: current_snapshot (getter and setter) and generate_next_frame().

  4. Write to_dict() and from_dict(), if needed. See documentation on storage for more.

  5. If the optional methods are needed for your engine, write them.