PySim Engine

This engine is a special case engine of the Python JSON Engine to link python APIs for different simulators (OpenAI, Mujoco, OpenSim, and Bullet) with NRP-core. The main difference between a PySim engine and a standard Python JSON engine is the way the Python APIs of several given simulators are called from a so-called SimulatorManager interface. The SimulatorManager packs different simulators into the same kinds of data interface to make it easier for using and combining different simulators in NRP_Core. As a whole, the PySim engine still runs a simulator model that specified the engine configuration and manages the synchronization and data exchange with other engines participating in the same experiment.

In that sense, it is used in a manner very similar to the Python JSON Engine (please refer to this guide for details on how to use it). Additionally, there are example experiments in the folder examples/pysim_examples that can be used as a reference for implementing experiments including this engine.

Similarly to the Python JSON Engine, the engine behavior in each experiment is implemented by subclassing a Python class PySimEngineScript and overriding the hook methods:

  • initialize() : executed when the engine is initialized

  • run_loop() : executed when the engine is requested to advance its simulation (Same as EngineScript.runLoop of Python JSON Engine)

  • shutdown() : executed when the engine is requested to shutdown

  • reset() : executed when the engine is requested to reset.

In this engine, several simulators (OpenAI, Mujoco, OpenSim, and Bullet) with python API are wrapped as functions in type of Python JSON Engine can apply. It includes shutdown, reset, runLoop, and calling model properties and standardizes the simulation process. The simulation launch with heavy timing cost is executed in the background of the initialize() function. In run_loop(), the interaction with the wrapped simulators is performed by an instance of the class SimulatorManager, which is stored in PySimEngineScript in the attribute self.sim_manager. This attribute must be “manually” called from the PySimEngineScript subclass in order to advance, modify or get information from the different simulators. As an example of this use, the script implementing PySimEngineScript in experiment examples/pysim_examples/opensim_control is listed below:

"""
A Py_Sim Engine for simulation
--> obtain information from simulation and send them to controller engine
--> receive controller command to run the simulation
"""
from nrp_core.engines.py_sim import PySimEngineScript

# The API of Opensim is shown in the following link:
# https://simtk.org/api_docs/opensim/api_docs


class Script(PySimEngineScript):

    def __init__(self):
        super().__init__()

        # To set the force of muscles, in arm_26, they are:
        # ['TRIlong', 'TRIlat', 'TRImed', 'BIClong', 'BICshort', 'BRA']
        # The default color of muscle in the visualizer is blue.
        # Once the force of a muscle is not the default value,
        # the color of the muscle will be changed.
        # Using this phenomenon, the controlled muscles can be found in the visualizer
        # For example, if action= [0.5, 0.0, 0.0, 0.0, 0.0, 0.0],
        # the color of TRIlong will not be blue in shown screen
        self.action = [0.0] * 6

    def initialize(self):
        print("OpensimEngine Server is initializing")
        print("Registering datapack --> sensors")
        self._registerDataPack("joints")
        self._setDataPack("joints", {"shoulder": 0, "elbow": 0})

        self._registerDataPack("infos")
        self._setDataPack("infos", {"time": 0})

        print("Registering datapack --> actuators")
        self._registerDataPack("control_cmd")

    def runLoop(self, timestep_ns):
        # Receive control data from TF
        self.action = self._getDataPack("control_cmd").get("act_list")
        reset_flag = self._getDataPack("control_cmd").get("reset")

        if reset_flag == 1:
            self.reset()
        else:
            # All Joints and Muscles can be found in the "*.osim"
            # Obtain the joint data from model "arm_26"
            # In arm_26, the joint set is [offset, r_shoulder, r_elbow]
            s_val = self.sim_manager.get_model_property("r_shoulder_elev", datapack_type="Joint")
            e_val = self.sim_manager.get_model_property("r_elbow_flex", datapack_type="Joint")
            # Send data to TF
            self._setDataPack("joints", {"shoulder": s_val, "elbow": e_val})
            self._setDataPack("infos", {"time": self.sim_manager.get_sim_time()})
        # Set muscles' force so to change joints
        self.sim_manager.run_step(self.action, timestep_ns)
        # To show components in the model changed by action
        # 1: To show components in a list
        # ctrl_list = self.sim_manager.theWorld.model.getControlsTable()
        # 2: To show components one by one
        # print(self.sim_manager.get_model_properties("Force"))

    def reset(self):
        print("Resetting Opensim simulation.")
        # Reset the value of set datapacks
        self._setDataPack("joints", {"shoulder": 0, "elbow": 0})
        self._setDataPack("infos", {"time": 0})
        # Reset simulation model
        self.sim_manager.reset()

    def shutdown(self):
        self.sim_manager.shutdown()
        print("Simulation engine is shutting down")

SimulatorManager

The SimulatorManager is a python class that packs different simulators (OpenSim, Mujoco, OpenAI,and Bullet) into the same interface and acts as a bridge to connect NRP-core and any of the supported simulators through the Python API.

It processes requests for simulation initialization, reset, shutdown, run_step, and data retrieval. When instantiated, it loads the simulation model specified in the engine configuration. Additionally, the SimulatorManager receives action for the run step of the simulator and observation data requirement in a run loop of the pysim engine. The observation data covers a variety of commonly used data types of the supported simulator. And users can based on simulators’ documents add more customized data types in the simulator APIs in “nrp_pysim_engines/nrp_pysim_engine/python”.

The following functions are provided by SimulatorManager to interact with the simulator:

  • run_step(action) : advances the simulation by the engine timestep as specified in the engine configuration. Takes as input an array of floats or dictionary. The length of input data must be equal to the number of controlled elements (such as muscles in Opensim) in the model.

  • reset() : resets the simulation

  • shutdown() : shutdowns the simulation

  • get_model_properties(p_type) : returns a list with the names of model’s elements of the type specified by p_type. The latter can take multiple possible values.

    • OpenAI: “Property” (the names of the observed elements)

    • Bullet: “Body”, “Joint”, “Link”

    • Mujoco: “body”, “joint”, “geom”, “site”, “light”, “camera”, “actuator”, “sensor”, “tendon”, and “mesh”

    • Opensim: “Joint” (the elements in the model JointSet) and “Force” (the elements in the model ForceSet)

  • get_model_all_properties(p_type) : returns a dictionary with the value of all elements of the type specified by p_type. The latter can take multiple possible values.

    • OpenAI: the same with p_type in get_model_properties

    • Bullet: the same with p_type in get_model_properties

    • Mujoco: supports all get_<p_type> in Mujoco Python API

    • Opensim: the same with p_type in get_model_properties

  • get_model_property(p_name, p_type) : returns the observed value for required the element p_name, and the p_type is the data type of the required element and is the same with p_type in get_model_all_properties

  • get_sim_time() : returns the simulation time in seconds

DataPacks

Similarly to the Python JSON engine, the PySim engine supports a unique datapack type: JsonDataPack. Refer to this section for more details.

Engine Configuration Parameters

The parameters for this engine are defined in the PySimEngine schema (listed here), which in turn is based on EngineBase and EngineJSON schemas, and thus inherits all parameters from them.

To use the Python Simulator engine in an experiment, set EngineType to “py_sim”.

Name

Description

Type

Default

Required

Array

EngineName

Name of the engine

string

X

EngineType

Engine type. Used by

string

X

EngineProcCmd

Engine Process Launch command

string

EngineProcStartParams

Engine Process Start Parameters

string

[]

X

EngineEnvParams

Engine Process Environment Parameters

string

[]

X

EngineLaunchCommand

object

{“LaunchType”:”BasicFork”}

EngineTimestep

Engine Timestep in seconds

number

0.01

EngineCommandTimeout

Engine Timeout (in seconds). It tells how long to wait for the completion of the engine runStep. 0 or negative values are interpreted as no timeout

number

0.0

Name

Description

Type

Default

Required

Array

ServerAddress

string

localhost:9002

RegistrationServerAddress

Address

string

localhost:9001

  • Parameters specific to this engine type:

Name

Description

Type

Default

Required

Array

PythonFileName

Path to the Python script containing the engine definition

string

X

WorldFileName

Path to the file of simulation world

string

X

Visualiser

To show the simulation in visualizer or not

bool

false

Simulator

To call the python API for a special simulator

string

Schema

As explained above, the schema used by the PySim engine inherits from EngineBase and EngineJSON schemas. A complete schema for the configuration of this engine is given below:

{"python_base" : {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Python Engine Base",
    "description": "Python Engine Base Configuration",
    "$id": "#PythonEngineBase",
    "allOf": [
      { "$ref": "json://nrp-core/engines/engine_comm_protocols.json#/engine_json" },
      {
        "properties": {
          "PythonFileName" : {
            "type": "string",
            "description": "Path to the python script containing the engine definition"
          }
        },
        "required": ["PythonFileName"]
      }
    ]
  },
  "python_json" : {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Python Json Engine",
    "description": "Python Json Engine Configuration",
    "$id": "#PythonJSONEngine",
    "allOf": [
      { "$ref": "#/python_base" },
      {
        "properties": {
          "EngineType": { "enum": ["python_json"] },
          "ServerOptions" : {
            "type": "string",
            "default": "",
            "description": "Additional options that will be used by the server (gunicorn) on startup. The string should contain a Python dictionary in the following format - \"{'key1': value, 'key2': 'value_str'}\". The full list of options can be found at the official page - https://docs.gunicorn.org/en/stable/settings.html."
          }
        }
      }
    ]
  },
  "python_grpc" : {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Python Grpc Engine",
    "description": "Python Grpc Engine Configuration",
    "$id": "#PythonGRPCEngine",
    "allOf": [
      { "$ref": "#/python_base" },
      {
        "properties": {
          "EngineType": { "enum": ["python_grpc"] }
        }
      }
    ]
  },
  "py_sim" : {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Python Simulation Engine",
    "description": "A simulation engine for simulators offering a Python API.",
    "$id": "#PySim",
    "allOf": [
      { "$ref": "#/python_base" },
      {
        "properties": {
          "EngineType": {
            "enum": ["py_sim"]
          },
          "ServerOptions" : {
            "type": "string",
            "default": "",
            "description": "Additional options that will be used by the server (gunicorn) on startup. The string should contain a Python dictionary in the following format - \"{'key1': value, 'key2': 'value_str'}\". The full list of options can be found at the official page - https://docs.gunicorn.org/en/stable/settings.html."
          },
          "Simulator": {
            "enum": ["Opensim","OpenAI","Mujoco","Bullet"],
            "description": "The simulators that are supported"
          },
          "WorldFileName": {
            "type": "string",
            "description": "Path to the file of simulation world"
          },
          "Visualizer": {
            "type": "boolean",
            "default": false,
            "description": "To show the simulation in visualizer or not"
          }
        },
        "required": ["Simulator", "WorldFileName"]
      }
    ]
  }
}