2022-06-16 18:29:50 +02:00
from functools import partial
2022-03-31 12:50:38 -07:00
from os import path
2022-08-16 16:59:18 +01:00
from typing import Optional , Union
2019-06-07 14:43:05 -07:00
2016-04-27 08:00:58 -07:00
import numpy as np
2022-03-31 12:50:38 -07:00
2016-04-27 08:00:58 -07:00
import gym
2022-05-24 08:47:51 -04:00
from gym import error , logger , spaces
2022-06-30 10:59:59 -04:00
from gym . spaces import Space
2022-06-08 00:20:56 +02:00
from gym . utils . renderer import Renderer
2016-04-27 08:00:58 -07:00
2022-07-06 11:18:03 -04:00
MUJOCO_PY_NOT_INSTALLED = False
MUJOCO_NOT_INSTALLED = False
2018-05-30 01:39:49 -07:00
2022-07-06 11:18:03 -04:00
try :
import mujoco_py
except ImportError as e :
MUJOCO_PY_IMPORT_ERROR = e
MUJOCO_PY_NOT_INSTALLED = True
2019-06-07 14:43:05 -07:00
2022-07-06 11:18:03 -04:00
try :
import mujoco
except ImportError as e :
MUJOCO_IMPORT_ERROR = e
MUJOCO_NOT_INSTALLED = True
2019-06-07 14:43:05 -07:00
2022-07-06 11:18:03 -04:00
DEFAULT_SIZE = 480
2019-06-07 14:43:05 -07:00
2022-07-06 11:18:03 -04:00
class BaseMujocoEnv ( gym . Env ) :
2021-07-29 02:26:34 +02:00
""" Superclass for all MuJoCo environments. """
2016-04-30 22:47:51 -07:00
2022-06-08 00:20:56 +02:00
def __init__ (
self ,
model_path ,
frame_skip ,
2022-06-30 10:59:59 -04:00
observation_space : Space ,
2022-06-08 00:20:56 +02:00
render_mode : Optional [ str ] = None ,
2022-06-16 18:29:50 +02:00
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
2022-06-08 00:20:56 +02:00
) :
2016-04-27 08:00:58 -07:00
if model_path . startswith ( " / " ) :
2022-07-06 11:18:03 -04:00
self . fullpath = model_path
2016-04-27 08:00:58 -07:00
else :
2022-07-06 11:18:03 -04:00
self . fullpath = path . join ( path . dirname ( __file__ ) , " assets " , model_path )
if not path . exists ( self . fullpath ) :
raise OSError ( f " File { self . fullpath } does not exist " )
2022-05-24 08:47:51 -04:00
2022-07-06 11:18:03 -04:00
self . _initialize_simulation ( )
2022-05-24 08:47:51 -04:00
self . init_qpos = self . data . qpos . ravel ( ) . copy ( )
self . init_qvel = self . data . qvel . ravel ( ) . copy ( )
self . _viewers = { }
2017-02-22 17:24:27 -08:00
self . frame_skip = frame_skip
2022-05-24 08:47:51 -04:00
2016-04-27 08:00:58 -07:00
self . viewer = None
2022-06-19 21:50:31 +01:00
assert self . metadata [ " render_modes " ] == [
" human " ,
" rgb_array " ,
" depth_array " ,
" single_rgb_array " ,
" single_depth_array " ,
]
assert (
int ( np . round ( 1.0 / self . dt ) ) == self . metadata [ " render_fps " ]
) , f ' Expected value: { int ( np . round ( 1.0 / self . dt ) ) } , Actual value: { self . metadata [ " render_fps " ] } '
2016-04-27 08:00:58 -07:00
2022-07-06 11:18:03 -04:00
self . observation_space = observation_space
2019-06-07 14:43:05 -07:00
self . _set_action_space ( )
2022-06-08 00:20:56 +02:00
self . render_mode = render_mode
2022-06-16 18:29:50 +02:00
render_frame = partial (
self . _render ,
width = width ,
height = height ,
camera_name = camera_name ,
camera_id = camera_id ,
)
self . renderer = Renderer ( self . render_mode , render_frame )
2022-06-08 00:20:56 +02:00
2019-06-07 14:43:05 -07:00
def _set_action_space ( self ) :
2020-02-29 01:11:29 +01:00
bounds = self . model . actuator_ctrlrange . copy ( ) . astype ( np . float32 )
2019-06-07 14:43:05 -07:00
low , high = bounds . T
2018-09-17 10:27:31 -07:00
self . action_space = spaces . Box ( low = low , high = high , dtype = np . float32 )
2019-06-07 14:43:05 -07:00
return self . action_space
2016-04-30 22:47:51 -07:00
# methods to override:
# ----------------------------
def reset_model ( self ) :
"""
Reset the robot degrees of freedom ( qpos and qvel ) .
Implement this in each subclass .
"""
raise NotImplementedError
def viewer_setup ( self ) :
"""
2018-09-24 14:53:23 -07:00
This method is called when the viewer is initialized .
2022-05-25 14:46:41 +01:00
Optionally implement this method , if you need to tinker with camera position and so forth .
2016-04-30 22:47:51 -07:00
"""
2022-07-06 11:18:03 -04:00
def _initialize_simulation ( self ) :
"""
Initialize MuJoCo simulation data structures mjModel and mjData .
"""
raise NotImplementedError
def _reset_simulation ( self ) :
"""
Reset MuJoCo simulation data structures , mjModel and mjData .
"""
raise NotImplementedError
def _step_mujoco_simulation ( self , ctrl , n_frames ) :
"""
2022-07-18 06:50:56 +10:00
Step over the MuJoCo simulation .
2022-07-06 11:18:03 -04:00
"""
raise NotImplementedError
def _render (
self ,
mode : str = " human " ,
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
) :
"""
Render a frame from the MuJoCo simulation as specified by the render_mode .
"""
raise NotImplementedError
2016-04-30 22:47:51 -07:00
# -----------------------------
2022-02-06 17:28:27 -06:00
def reset (
self ,
* ,
seed : Optional [ int ] = None ,
return_info : bool = False ,
options : Optional [ dict ] = None ,
) :
2021-12-08 22:14:15 +01:00
super ( ) . reset ( seed = seed )
2022-05-24 08:47:51 -04:00
2022-07-06 11:18:03 -04:00
self . _reset_simulation ( )
2022-05-24 08:47:51 -04:00
2016-04-30 22:47:51 -07:00
ob = self . reset_model ( )
2022-06-08 00:20:56 +02:00
self . renderer . reset ( )
self . renderer . render_step ( )
2022-02-06 17:28:27 -06:00
if not return_info :
return ob
else :
return ob , { }
2016-04-30 22:47:51 -07:00
def set_state ( self , qpos , qvel ) :
2022-07-06 11:18:03 -04:00
"""
Set the joints position qpos and velocity qvel of the model . Override this method depending on the MuJoCo bindings used .
"""
2016-04-30 22:47:51 -07:00
assert qpos . shape == ( self . model . nq , ) and qvel . shape == ( self . model . nv , )
2016-04-27 08:00:58 -07:00
@property
def dt ( self ) :
return self . model . opt . timestep * self . frame_skip
def do_simulation ( self , ctrl , n_frames ) :
2022-07-06 11:18:03 -04:00
"""
Step the simulation n number of frames and applying a control action .
"""
# Check control input is contained in the action space
2021-11-18 07:11:40 +08:00
if np . array ( ctrl ) . shape != self . action_space . shape :
raise ValueError ( " Action dimension mismatch " )
2022-07-06 11:18:03 -04:00
self . _step_mujoco_simulation ( ctrl , n_frames )
2016-04-27 08:00:58 -07:00
2022-08-22 17:21:08 +02:00
def render ( self ) :
return self . renderer . get_renders ( )
2022-06-08 00:20:56 +02:00
2022-07-06 11:18:03 -04:00
def close ( self ) :
if self . viewer is not None :
self . viewer = None
self . _viewers = { }
def get_body_com ( self , body_name ) :
""" Return the cartesian position of a body frame """
raise NotImplementedError
def state_vector ( self ) :
""" Return the position and velocity joint states of the model """
return np . concatenate ( [ self . data . qpos . flat , self . data . qvel . flat ] )
class MuJocoPyEnv ( BaseMujocoEnv ) :
def __init__ (
self ,
model_path : str ,
frame_skip : int ,
observation_space : Space ,
render_mode : Optional [ str ] = None ,
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
) :
if MUJOCO_PY_NOT_INSTALLED :
raise error . DependencyNotInstalled (
f " { MUJOCO_PY_IMPORT_ERROR } . (HINT: you need to install mujoco_py, and also perform the setup instructions here: https://github.com/openai/mujoco-py/.) "
)
logger . warn (
" This version of the mujoco environments depends "
" on the mujoco-py bindings, which are no longer maintained "
" and may stop working. Please upgrade to the v4 versions of "
" the environments (which depend on the mujoco python bindings instead), unless "
" you are trying to precisely replicate previous works). "
)
super ( ) . __init__ (
model_path ,
frame_skip ,
observation_space ,
render_mode ,
width ,
height ,
camera_id ,
camera_name ,
)
def _initialize_simulation ( self ) :
self . model = mujoco_py . load_model_from_path ( self . fullpath )
self . sim = mujoco_py . MjSim ( self . model )
self . data = self . sim . data
def _reset_simulation ( self ) :
self . sim . reset ( )
def set_state ( self , qpos , qvel ) :
super ( ) . set_state ( qpos , qvel )
state = self . sim . get_state ( )
state = mujoco_py . MjSimState ( state . time , qpos , qvel , state . act , state . udd_state )
self . sim . set_state ( state )
self . sim . forward ( )
def _step_mujoco_simulation ( self , ctrl , n_frames ) :
self . sim . data . ctrl [ : ] = ctrl
for _ in range ( n_frames ) :
self . sim . step ( )
def _render (
self ,
mode : str = " human " ,
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
) :
assert mode in self . metadata [ " render_modes " ]
if mode in {
" rgb_array " ,
" single_rgb_array " ,
" depth_array " ,
" single_depth_array " ,
} :
if camera_id is not None and camera_name is not None :
raise ValueError (
" Both `camera_id` and `camera_name` cannot be "
" specified at the same time. "
)
no_camera_specified = camera_name is None and camera_id is None
if no_camera_specified :
camera_name = " track "
if camera_id is None and camera_name in self . model . _camera_name2id :
if camera_name in self . model . _camera_name2id :
camera_id = self . model . camera_name2id ( camera_name )
self . _get_viewer ( mode ) . render ( width , height , camera_id = camera_id )
if mode in { " rgb_array " , " single_rgb_array " } :
data = self . _get_viewer ( mode ) . read_pixels ( width , height , depth = False )
# original image is upside-down, so flip it
return data [ : : - 1 , : , : ]
elif mode in { " depth_array " , " single_depth_array " } :
self . _get_viewer ( mode ) . render ( width , height )
# Extract depth part of the read_pixels() tuple
data = self . _get_viewer ( mode ) . read_pixels ( width , height , depth = True ) [ 1 ]
# original image is upside-down, so flip it
return data [ : : - 1 , : ]
elif mode == " human " :
self . _get_viewer ( mode ) . render ( )
2022-08-16 16:59:18 +01:00
def _get_viewer (
self , mode
) - > Union [ " mujoco_py.MjViewer " , " mujoco_py.MjRenderContextOffscreen " ] :
2022-07-06 11:18:03 -04:00
self . viewer = self . _viewers . get ( mode )
if self . viewer is None :
if mode == " human " :
self . viewer = mujoco_py . MjViewer ( self . sim )
elif mode in {
" rgb_array " ,
" depth_array " ,
" single_rgb_array " ,
" single_depth_array " ,
} :
self . viewer = mujoco_py . MjRenderContextOffscreen ( self . sim , - 1 )
2022-08-16 16:59:18 +01:00
else :
raise AttributeError (
f " Unknown mode: { mode } , expected modes: { self . metadata [ ' render_modes ' ] } "
)
2022-07-06 11:18:03 -04:00
self . viewer_setup ( )
self . _viewers [ mode ] = self . viewer
2022-08-16 16:59:18 +01:00
2022-07-06 11:18:03 -04:00
return self . viewer
def get_body_com ( self , body_name ) :
return self . data . get_body_xpos ( body_name )
class MujocoEnv ( BaseMujocoEnv ) :
""" Superclass for MuJoCo environments. """
def __init__ (
self ,
model_path ,
frame_skip ,
observation_space : Space ,
render_mode : Optional [ str ] = None ,
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
) :
if MUJOCO_NOT_INSTALLED :
raise error . DependencyNotInstalled (
f " { MUJOCO_IMPORT_ERROR } . (HINT: you need to install mujoco) "
)
super ( ) . __init__ (
model_path ,
frame_skip ,
observation_space ,
render_mode ,
width ,
height ,
camera_id ,
camera_name ,
)
def _initialize_simulation ( self ) :
self . model = mujoco . MjModel . from_xml_path ( self . fullpath )
self . data = mujoco . MjData ( self . model )
def _reset_simulation ( self ) :
mujoco . mj_resetData ( self . model , self . data )
def set_state ( self , qpos , qvel ) :
super ( ) . set_state ( qpos , qvel )
self . data . qpos [ : ] = np . copy ( qpos )
self . data . qvel [ : ] = np . copy ( qvel )
if self . model . na == 0 :
self . data . act [ : ] = None
mujoco . mj_forward ( self . model , self . data )
def _step_mujoco_simulation ( self , ctrl , n_frames ) :
self . data . ctrl [ : ] = ctrl
mujoco . mj_step ( self . model , self . data , nstep = self . frame_skip )
# As of MuJoCo 2.0, force-related quantities like cacc are not computed
# unless there's a force sensor in the model.
# See https://github.com/openai/gym/issues/1541
mujoco . mj_rnePostConstraint ( self . model , self . data )
2022-06-08 00:20:56 +02:00
def _render (
self ,
2022-06-16 18:29:50 +02:00
mode : str = " human " ,
width : int = DEFAULT_SIZE ,
height : int = DEFAULT_SIZE ,
camera_id : Optional [ int ] = None ,
camera_name : Optional [ str ] = None ,
2022-06-08 00:20:56 +02:00
) :
assert mode in self . metadata [ " render_modes " ]
if mode in {
" rgb_array " ,
" single_rgb_array " ,
" depth_array " ,
" single_depth_array " ,
} :
2019-08-23 15:02:33 -07:00
if camera_id is not None and camera_name is not None :
2021-07-29 15:39:42 -04:00
raise ValueError (
" Both `camera_id` and `camera_name` cannot be "
" specified at the same time. "
)
2019-08-23 15:02:33 -07:00
no_camera_specified = camera_name is None and camera_id is None
if no_camera_specified :
2021-07-29 02:26:34 +02:00
camera_name = " track "
2019-08-23 15:02:33 -07:00
2022-05-24 08:47:51 -04:00
if camera_id is None :
2022-07-06 11:18:03 -04:00
camera_id = mujoco . mj_name2id (
self . model ,
mujoco . mjtObj . mjOBJ_CAMERA ,
camera_name ,
)
2019-08-23 15:02:33 -07:00
2022-05-24 08:47:51 -04:00
self . _get_viewer ( mode ) . render ( width , height , camera_id = camera_id )
2020-06-20 06:42:26 +09:00
2022-06-08 00:20:56 +02:00
if mode in { " rgb_array " , " single_rgb_array " } :
2018-05-30 01:39:49 -07:00
data = self . _get_viewer ( mode ) . read_pixels ( width , height , depth = False )
2018-02-26 01:48:44 -08:00
# original image is upside-down, so flip it
return data [ : : - 1 , : , : ]
2022-06-08 00:20:56 +02:00
elif mode in { " depth_array " , " single_depth_array " } :
2018-09-21 01:51:58 +01:00
self . _get_viewer ( mode ) . render ( width , height )
# Extract depth part of the read_pixels() tuple
data = self . _get_viewer ( mode ) . read_pixels ( width , height , depth = True ) [ 1 ]
# original image is upside-down, so flip it
return data [ : : - 1 , : ]
2021-07-29 02:26:34 +02:00
elif mode == " human " :
2018-05-30 01:39:49 -07:00
self . _get_viewer ( mode ) . render ( )
2016-04-27 08:00:58 -07:00
Cleanup, removal of unmaintained code (#836)
* add dtype to Box
* remove board_game, debugging, safety, parameter_tuning environments
* massive set of breaking changes
- remove python logging module
- _step, _reset, _seed, _close => non underscored method
- remove benchmark and scoring folder
* Improve render("human"), now resizable, closable window.
* get rid of default step and reset in wrappers, so it doesn’t silently fail for people with underscore methods
* CubeCrash unit test environment
* followup fixes
* MemorizeDigits unit test envrionment
* refactored spaces a bit
fixed indentation
disabled test_env_semantics
* fix unit tests
* fixes
* CubeCrash, MemorizeDigits tested
* gym backwards compatibility patch
* gym backwards compatibility, followup fixes
* changelist, add spaces to main namespaces
* undo_logger_setup for backwards compat
* remove configuration.py
2018-01-25 18:20:14 -08:00
def close ( self ) :
if self . viewer is not None :
2022-07-06 11:18:03 -04:00
self . viewer . close ( )
super ( ) . close ( )
Cleanup, removal of unmaintained code (#836)
* add dtype to Box
* remove board_game, debugging, safety, parameter_tuning environments
* massive set of breaking changes
- remove python logging module
- _step, _reset, _seed, _close => non underscored method
- remove benchmark and scoring folder
* Improve render("human"), now resizable, closable window.
* get rid of default step and reset in wrappers, so it doesn’t silently fail for people with underscore methods
* CubeCrash unit test environment
* followup fixes
* MemorizeDigits unit test envrionment
* refactored spaces a bit
fixed indentation
disabled test_env_semantics
* fix unit tests
* fixes
* CubeCrash, MemorizeDigits tested
* gym backwards compatibility patch
* gym backwards compatibility, followup fixes
* changelist, add spaces to main namespaces
* undo_logger_setup for backwards compat
* remove configuration.py
2018-01-25 18:20:14 -08:00
2022-08-16 16:59:18 +01:00
def _get_viewer (
self , mode , width = DEFAULT_SIZE , height = DEFAULT_SIZE
) - > Union [ " gym.envs.mujoco.Viewer " , " gym.envs.mujoco.RenderContextOffscreen " ] :
2018-05-30 01:39:49 -07:00
self . viewer = self . _viewers . get ( mode )
2016-04-27 08:00:58 -07:00
if self . viewer is None :
2021-07-29 02:26:34 +02:00
if mode == " human " :
2022-07-06 11:18:03 -04:00
from gym . envs . mujoco import Viewer
2022-05-24 08:47:51 -04:00
2022-07-06 11:18:03 -04:00
self . viewer = Viewer ( self . model , self . data )
2022-06-08 00:20:56 +02:00
elif mode in {
" rgb_array " ,
" depth_array " ,
" single_rgb_array " ,
" single_depth_array " ,
} :
2022-07-06 11:18:03 -04:00
from gym . envs . mujoco import RenderContextOffscreen
2022-05-24 08:47:51 -04:00
2022-07-06 11:18:03 -04:00
self . viewer = RenderContextOffscreen (
width , height , self . model , self . data
)
2022-08-16 16:59:18 +01:00
else :
raise AttributeError (
f " Unexpected mode: { mode } , expected modes: { self . metadata [ ' render_modes ' ] } "
)
2019-06-07 14:43:05 -07:00
2016-04-27 08:00:58 -07:00
self . viewer_setup ( )
2018-05-30 01:39:49 -07:00
self . _viewers [ mode ] = self . viewer
2016-04-27 08:00:58 -07:00
return self . viewer
def get_body_com ( self , body_name ) :
2022-07-06 11:18:03 -04:00
return self . data . body ( body_name ) . xpos