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 initializedrun_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 shutdownreset()
: 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 simulationshutdown()
: shutdowns the simulationget_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 modelForceSet
)
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
inget_model_properties
Bullet: the same with
p_type
inget_model_properties
Mujoco: supports all
get_<p_type>
in Mujoco Python APIOpensim: the same with
p_type
inget_model_properties
get_model_property(p_name, p_type)
: returns the observed value for required the elementp_name
, and thep_type
is the data type of the required element and is the same withp_type
inget_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”.
Parameters inherited from EngineBase schema:
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 |
Parameters inherited from EngineJSON schema:
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"] } ] } }