NRP Core  1.4.1
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".

NameDescriptionTypeDefaultRequiredArray
EngineNameName of the enginestringX
EngineTypeEngine type. Used by EngineLauncherManager to select the correct engine launcherstringX
EngineProcCmdEngine Process Launch commandstring
EngineProcStartParamsEngine Process Start Parametersstring[]X
EngineEnvParamsEngine Process Environment Parametersstring[]X
EngineLaunchCommandLaunchCommand with parameters that will be used to launch the engine processobject{"LaunchType":"BasicFork"}
EngineTimestepEngine Timestep in secondsnumber0.01
EngineCommandTimeoutEngine Timeout (in seconds). It tells how long to wait for the completion of the engine runStep. 0 or negative values are interpreted as no timeoutnumber0.0
NameDescriptionTypeDefaultRequiredArray
ServerAddressEngineJSONServer address. Should this address already be in use, the server will continue trying ports higher upstringlocalhost:9002
RegistrationServerAddressAddress EngineJSONRegistrationServer is listening at. Once the JSON engine server has bound to a port, it will use this address to register itself with the SimulationManagerstringlocalhost:9001
  • Parameters specific to this engine type:
NameDescriptionTypeDefaultRequiredArray
PythonFileNamePath to the Python script containing the engine definitionstringX
WorldFileNamePath to the file of simulation worldstringX
VisualiserTo show the simulation in visualizer or notboolfalse
SimulatorTo call the python API for a special simulatorstring

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"]
}
]
}
}
python_json_engine.shutdown
def shutdown()
Definition: python_json_engine.py:104
python_json_engine.reset
def reset()
Definition: python_json_engine.py:99
python_json_engine.initialize
def initialize()
Definition: python_json_engine.py:79