- A Tutorial for Mujoco For Me
reference: https://github.com/HILMR/LearnMujoco
Mujoco is an open software now, and it can be installed through pip:
pip install mujoco
mujoco_py has be deprecated.
It's recommended to install dm_control
for convenience.
pip install dm_control
- Official Tutorial for Python: - mujoco - dm_control
- Official Website: how to write the configuration files and how to use the api
The tutorial is bad... They use mediapy to replay the scene after the simulation and I can not run the demo. Thus, this section will not mention any about how to render the scene in Mujoco interactive viewer.ops, there is a section introducing the interactive viewer, haha :)
mujoco.MjModel.from_xml_string("xml")
: create a binary instance directly from xml description.
xml string is written in MuJoCo's MjCF, which is an XML-based modeling language.
mujoco.MjModel.from_xml_path(path_to_file)
: create a binary instance from a xml file.mujoco.MjModel.from_binary_path
:...
A model configuration file example:
<mujoco>
<worldbody>
<light name="top" pos="0 0 1" />
<geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1" />
<geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1" />
</worldbody>
</mujoco>
All physical elements live inside the <worldbody>
.
The default position is
0 0 0
, the default geom type type is sphere. For more detail, see here.
MjModel
contains the model description. The complete description can be found at the end of the header file mjmodel.h.
example: model.ngeom
. model.geom_rgba
model.geom('green_sphere')
will tell us all the valid properties.model.geom('green_sphere').rgba
will give the value of rgba.mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_GEOM, 'green_sphere')
will give the id of instance which can be used to indexed likemodel.geom_rgba[id, :]
.
Note that the 0th body is always the world. It cannot be renamed.
These classes provide access to the raw memory used by MuJoCo without copying or buffering. The user is advised to create copies where required, for instance,
position.append(data.body('my_body').xpos.copy())
.MjData
contains the state and quantities that depend on it. The state is made up of time, generalized position and generalized velocities. These are respectivelydata.time
,data.qpos
anddata.qvel
. To make a newMjData
. we can
data = mujoco.MjData(model)
To initialize the environment, we need explicitly propagate the derived quantities in mjData
using mj_kinematics
which computes global Cartesian poses for all objects (excluding cameras and lights).
mujoco.mj_kinematics(model, data)
To propagate the values in mjData
for dynamic, we need
mujoco.mj_forward(model, data)
Next we introduce the common members of mjData.
It is the generalized position. ([pos, quat] for freejoint)
Standalone application:
python -m mujoco.viewer
launches an empty visualization session.python -m mujoco.viewer --mjcf=/path/to/some/mjcf.xml
launches a visualization session for the model file.
Manage viewer:
This function blocks user code to support precise timing of the physics loop.
viewer.launch()
launches a empty visualization session.viewer.launch(model)
launches a visualization session for the givenmjModel
.viewer.launch(model, data)
has same function as above except it operates directly on the givenmjData
instance.
Usage:
import mujoco.viewer
mujoco.viewer.launch([model, [data]])
Passive viewer:
This function will not block, allowing user code to continue execution. The user's script is responsible for timing and advancing the physics state, and mouse-drag perturbations will not work.
import time
import mujoco
import mujoco.viewer
m = mujoco.MjModel.from_xml_path('/path/to/mjcf.xml')
d = mujoco.MjData(m)
with mujoco.viewer.launch_passive(m, d) as viewer:
# Close the viewer automatically after 30 wall-seconds.
start = time.time()
while viewer.is_running() and time.time() - start < 30:
step_start = time.time()
# mj_step can be replaced with code that also evaluates
# a policy and applies a control signal before stepping the physics.
mujoco.mj_step(m, d)
# Example modification of a viewer option: toggle contact points every two seconds.
with viewer.lock():
viewer.opt.flags[mujoco.mjtVisFlag.mjVIS_CONTACTPOINT] = int(d.time % 2)
# Pick up changes to the physics state, apply perturbations, update options from GUI.
viewer.sync()
# Rudimentary time keeping, will drift relative to wall clock.
time_until_next_step = m.opt.timestep - (time.time() - step_start)
if time_until_next_step > 0:
time.sleep(time_until_next_step)
launch_passive
function returns a handle which can be used to interact with the viewer, it has the following attributes:
scn
,cam
,opt
andperrt
properties: correspond to mjvScene, mjvCamera, mjvOption, mjvPerturb structs respectively.lock()
: User code must ensure that it is holding the viewer lock before modifying any physic or visualization state, includingmhModel
andmjData
, and above four properties of viewer.sync()
: synchronizes state betweenmjModel
,mjData
and GUI user inputs since the previous call tosync
.close()
: closes the viewer window.is_runing()
: returnTrue
is the viewer window is runnig.
Flow: First, get a policy and then make a step, [change any state after lock] and then run
sync
and wait until the rest time running out.
In order to calling any of mjr_
rendering routine, users are expected to set up a working OpenGL context, for instance,
ctx = mujoco.GLContext(max_width, max_height)
ctx.make_current()
.
.
.
ctx.free() # free the context.
...
MuJoCo allows users to install custom callback functions to modify certain parts of its computation pipeline. For example, mjcb_sensor
can be used to implement custom sensors, and mjcb_control
can be used to implement custom actuators.
Using MuJoCo's main high level function mj_step
to step the state
mujoco.mj_step(model, data)
To make things move, we need to add DoFs to model by adding joints to bodies, specifying how they can move with respect to their parents.
Example:
<mujoco>
<worldbody>
<light name="top" pos="0 0 1"/>
<option gravity="0 0 -9.81" />
<body name="box_and_sphere" euler="0 0 -30">
<joint name="swing" type="hinge" axis="1 -1 0" pos="-.2 -.2 -.2"/>
<geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
<geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
</body>
</worldbody>
</mujoco>
To visualize the joint axis, one can use visualization option object MjOption
.
scene_option = mujoco.MjvOption()
scene_option.flagsp[mujoco.mjtVisFlag.mjVIS_JOINT] = True
Physics options live in mhModel.opt, for example: model.opt.timestep
, model.opt.gravity
.
The options can be modified. We can modify these in XML using the top-level
<option>
elements
MuJoCo uses a representation known as the "Lagrangian", "generalized" or "additive" representation, whereby objects have no DoFs unless explicitly added using joints.
print('Total number of DoFs in the model:', model.nv)
print('Generalized positions:', data.qpos)
print('Generalized velocities:', data.qvel)
MuJoCo functions are exposed as Python functions of the same name. In Python, the size arguments are omitted since we can automatically (and indeed, more safely) deduce it from the NumPy array. When calling these functions, pass all arguments other than array sizes in the same order as they appear in mujoco.h.
MuJoCo enums are available as mujoco.mjtEnumType.ENUM_VALUE
, for example mujoco.mjtObj.mjOBJ_SITE
. MuJoCo constants are available with the same name directly under the mujoco module, for example mujoco.mjVISSTRING
.
MuJoCo simulations are deterministic with one exception: sensor noise can be generated when this feature is enabled. This is done by calling the C function rand() internally. To generate the same random number sequence, call srand() with a desired seed after the model is loaded and before the simulation starts. The model compiler calls srand(123) internally, so as to generate random dots for procedural textures. Therefore the noise sequence in the sensor data will change if the specification of procedural textures changes, and the user does not call srand() after model compilation.
from dm_control import suite
from dm_control import viewer
# Load an environment from the Control Suite.
env = suite.load(domain_name="humanoid", task_name="stand")
# Launch the viewer application.
viewer.launch(env)
from dm_control import suite
from dm_control import viewer
import numpy as np
env = suite.load(domain_name="humanoid", task_name="stand")
action_spec = env.action_spec()
# Define a uniform random policy.
def random_policy(time_step):
del time_step # Unused.
return np.random.uniform(low=action_spec.minimum,
high=action_spec.maximum,
size=action_spec.shape)
# Launch the viewer application.
viewer.launch(env, policy=random_policy)
<mujoco model="tippe top">
<option integrator="RK4"/>
<asset>
<texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"
rgb2=".2 .3 .4" width="300" height="300"/>
<material name="grid" texture="grid" texrepeat="8 8" reflectance=".2"/>
</asset>
<worldbody>
<geom size=".2 .2 .01" type="plane" material="grid"/>
<light pos="0 0 .6"/>
<camera name="closeup" pos="0 -.1 .07" xyaxes="1 0 0 0 1 2"/>
<body name="top" pos="0 0 .02">
<freejoint/>
<geom name="ball" type="sphere" size=".02" />
<geom name="stem" type="cylinder" pos="0 0 .02" size="0.004 .008"/>
<geom name="ballast" type="box" size=".023 .023 0.005" pos="0 0 -.015"
contype="0" conaffinity="0" group="3"/>
</body>
</worldbody>
<keyframe>
<key name="spinning" qpos="0 0 0.02 1 0 0 0" qvel="0 0 0 0 1 200" />
</keyframe>
</mujoco>
qpos
has 7 elements, first three are position of body, the rest are unit quaternion.
Notes:
- Using
<option/>
clause to set the integrator. - Using
<asset/>
clause to define floor's grid material. - Using
<freejoint/>
clause to add 6-Dof joint. - We use an invisible and non-colliding box geom called
ballast
to move the top's center-of-mass lower. Having a low center of mass is (counter-intuitively) required for the flipping behavior to occur. - We save our initial spinning state as a keyframe. It has a high rotational velocity around the Z-axis, but is not perfectly oriented with the world, which introduces the symmetry-breaking required for the flipping.
keyframe can be set by
mujoco.mj_resetDataKeyframe(model, data, 0)
.
- We define a
<camera>
in our model, and then render from it using thecamera
argument toupdate_scene()
.
...
...
<mujoco>
<asset>
<texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"
rgb2=".2 .3 .4" width="300" height="300" mark="none"/>
<material name="grid" texture="grid" texrepeat="6 6"
texuniform="true" reflectance=".2"/>
<material name="wall" rgba='.5 .5 .5 1'/>
</asset>
<default>
<geom type="box" size=".05 .05 .05" />
<joint type="free"/>
</default>
<worldbody>
<light name="light" pos="-.2 0 1"/>
<geom name="ground" type="plane" size=".5 .5 10" material="grid"
zaxis="-.3 0 1" friction=".1"/>
<camera name="y" pos="-.1 -.6 .3" xyaxes="1 0 0 0 1 2"/>
<body pos="0 0 .1">
<joint/>
<geom/>
</body>
<body pos="0 .2 .1">
<joint/>
<geom friction=".33"/>
</body>
</worldbody>
</mujoco>
<mujoco>
<asset>
<texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"
rgb2=".2 .3 .4" width="300" height="300" mark="none"/>
<material name="grid" texture="grid" texrepeat="1 1"
texuniform="true" reflectance=".2"/>
</asset>
<worldbody>
<light name="light" pos="0 0 1"/>
<geom name="floor" type="plane" pos="0 0 -.5" size="2 2 .1" material="grid"/>
<site name="anchor" pos="0 0 .3" size=".01"/>
<camera name="fixed" pos="0 -1.3 .5" xyaxes="1 0 0 0 1 2"/>
<geom name="pole" type="cylinder" fromto=".3 0 -.5 .3 0 -.1" size=".04"/>
<body name="bat" pos=".3 0 -.1">
<joint name="swing" type="hinge" damping="1" axis="0 0 1"/>
<geom name="bat" type="capsule" fromto="0 0 .04 0 -.3 .04"
size=".04" rgba="0 0 1 1"/>
</body>
<body name="box_and_sphere" pos="0 0 0">
<joint name="free" type="free"/>
<geom name="red_box" type="box" size=".1 .1 .1" rgba="1 0 0 1"/>
<geom name="green_sphere" size=".06" pos=".1 .1 .1" rgba="0 1 0 1"/>
<site name="hook" pos="-.1 -.1 -.1" size=".01"/>
<site name="IMU"/>
</body>
</worldbody>
<tendon>
<spatial name="wire" limited="true" range="0 0.35" width="0.003">
<site site="anchor"/>
<site site="hook"/>
</spatial>
</tendon>
<actuator>
<motor name="my_motor" joint="swing" gear="1"/>
</actuator>
<sensor>
<accelerometer name="accelerometer" site="IMU"/>
</sensor>
</mujoco>