Domain Randomization#
Domain randomization varies physical parameters during training so that policies
are robust to modeling errors and real-world variation. This guide shows
how to attach randomization terms to your environment using EventTerm and
mdp.randomize_field.
TL;DR#
Use an EventTerm that calls mdp.randomize_field with a target field, a
value range (or per-axis ranges), and an operation describing how to
apply the draw.
from mjlab.managers.manager_term_config import EventTermCfg as EventTerm
from mjlab.managers.manager_term_config import term
from mjlab.managers.scene_entity_config import SceneEntityCfg
from mjlab.envs import mdp
foot_friction: EventTerm = term(
EventTerm,
mode="reset", # randomize each episode
func=mdp.randomize_field,
domain_randomization=True, # marks this as domain randomization
params={
"asset_cfg": SceneEntityCfg("robot", geom_names=[".*_foot.*"]),
"field": "geom_friction",
"ranges": (0.3, 1.2),
"operation": "abs",
},
)
Domain Randomization Flag#
When creating an EventTerm for domain randomization, set domain_randomization=True.
This allows the environment to track which fields are being randomized:
EventTerm(
mode="reset",
func=mdp.randomize_field,
domain_randomization=True, # required for DR tracking
params={"field": "geom_friction", ...},
)
This flag is especially useful when using custom class-based event terms instead of
mdp.randomize_field.
Event Modes#
"startup"- randomize once at initialization"reset"- randomize at every episode reset"interval"- randomize at regular time intervals
Available Fields#
Joint/DOF: dof_armature, dof_frictionloss, dof_damping, jnt_range,
jnt_stiffness, qpos0
Body: body_mass, body_ipos, body_iquat, body_inertia, body_pos,
body_quat
Geom: geom_friction, geom_pos, geom_quat, geom_rgba
Site: site_pos, site_quat
Randomization Parameters#
Distribution: "uniform" (default), "log_uniform" (values must be > 0),
"gaussian" (mean, std)
Operation: "abs" (default, set), "scale" (multiply), "add" (offset)
Axis selection#
Multi-dimensional fields can be randomized per-axis.
Friction. Geoms have three coefficients [tangential, torsional, rolling].
For condim=3 (standard frictional contact), only axis 0 (tangential)
affects contact behavior:
# Tangential friction (affects condim=3)
params={"field": "geom_friction", "ranges": {0: (0.3, 1.2)}}
# Tangential + torsional (torsional matters for condim >= 4)
params={"field": "geom_friction", "ranges": {0: (0.5, 1.0), 1: (0.001, 0.01)}}
# X and Y position
params={"field": "body_pos", "axes": [0, 1], "ranges": (-0.1, 0.1)}
Examples#
Friction (reset)#
foot_friction: EventTerm = term(
EventTerm,
mode="reset",
func=mdp.randomize_field,
domain_randomization=True,
params={
"asset_cfg": SceneEntityCfg("robot", geom_names=[".*_foot.*"]),
"field": "geom_friction",
"ranges": (0.3, 1.2),
"operation": "abs",
},
)
Note
Give your robot’s collision geoms higher priority than terrain (geom priority defaults to 0). Then you only need to randomize robot friction. MuJoCo will use the higher-priority geom’s friction in (robot, terrain) contacts.
from mjlab.utils.spec_config import CollisionCfg
robot_collision = CollisionCfg(
geom_names_expr=[".*_foot.*"],
priority=1,
friction=(0.6,),
condim=3,
)
Joint Offset (startup)#
Randomize default joint positions to simulate joint offset calibration errors:
joint_offset: EventTerm = term(
EventTerm,
mode="startup",
func=mdp.randomize_field,
domain_randomization=True,
params={
"asset_cfg": SceneEntityCfg("robot", joint_names=[".*"]),
"field": "qpos0",
"ranges": (-0.01, 0.01),
"operation": "add",
},
)
Center of Mass (COM) (startup)#
com: EventTerm = term(
EventTerm,
mode="startup",
func=mdp.randomize_field,
domain_randomization=True,
params={
"asset_cfg": SceneEntityCfg("robot", body_names=["torso"]),
"field": "body_ipos",
"ranges": {0: (-0.02, 0.02), 1: (-0.02, 0.02)},
"operation": "add",
},
)
Custom Class-Based Event Terms#
You can create custom event terms using classes instead of functions. This is useful for event terms that need to maintain state or perform initialization logic:
class RandomizeTerrainFriction:
"""Custom event term that randomizes terrain friction."""
def __init__(self, cfg, env):
# Find the terrain geom index during initialization
self._terrain_idx = None
for idx, geom in enumerate(env.scene.spec.geoms):
if geom.name == "terrain":
self._terrain_idx = idx
if self._terrain_idx is None:
raise ValueError("Terrain geom not found in the model.")
def __call__(self, env, env_ids, ranges):
"""Called each time the event is triggered."""
from mjlab.utils.math import sample_uniform
env.sim.model.geom_friction[env_ids, self._terrain_idx, 0] = sample_uniform(
ranges[0], ranges[1], len(env_ids), env.device
)
# Use the custom class in your environment config
terrain_friction: EventTerm = term(
EventTerm,
mode="reset",
func=RandomizeTerrainFriction,
domain_randomization=True,
params={"field": "geom_friction", "ranges": (0.3, 1.2)},
)
Migrating from Isaac Lab#
Isaac Lab exposes explicit friction combination modes (multiply, average,
min, max). MuJoCo instead uses priority-based selection: if one
contacting geom has higher priority, its friction is used; otherwise the
element-wise maximum is used. See the
MuJoCo contact documentation
for details.