Implementation:Google deepmind Dm control Composer Variation
| Attribute | Value |
|---|---|
| Implementation | Composer Variation |
| Workflow | Composer_Environment_Building |
| Domain | Reinforcement_Learning, Sim_to_Real |
| Source | dm_control |
| Last Updated | 2026-02-15 00:00 GMT |
Overview
Concrete tool for applying stochastic domain randomization to MJCF model attributes and compiled physics parameters in dm_control Composer environments through the MJCFVariator, PhysicsVariator, distribution classes, and the evaluate utility.
Description
The dm_control.composer.variation package provides a complete system for domain randomization:
Variator classes (in dm_control/composer/variation/__init__.py):
MJCFVariator-- binds variation callables to attributes ofmjcf.Elementobjects. Whenapply_variations(random_state)is called, each attribute is set to the result of evaluating its variation. The variator records the initial value of each attribute on first application so that relative variations work correctly. Meant to be called ininitialize_episode_mjcf(before compilation).PhysicsVariator-- similar toMJCFVariatorbut operates on compiled physics bindings (physics.bind(element)). Meant to be called ininitialize_episode(after compilation), avoiding recompilation for parameters that exist in the compiled model.
Both variators provide clear() to remove all bindings and reset_initial_values() to forget cached initial values.
Distribution classes (in dm_control/composer/variation/distributions.py):
Uniform(low=0.0, high=1.0)-- uniform distribution.UniformInteger(low, high=None)-- uniform integer distribution.UniformChoice(choices)-- uniform choice from a list.UniformPointOnSphere()-- uniform unit vector on the 3D sphere.Normal(loc=0.0, scale=1.0)-- Gaussian distribution.LogNormal(mean=0.0, sigma=1.0)-- log-normal distribution.Exponential(scale=1.0)-- exponential distribution.Poisson(lam=1.0)-- Poisson distribution.Bernoulli(prob=0.5)-- Bernoulli (coin flip) distribution.BiasedRandomWalk(stdev=0.1, timescale=10.0)-- stateful Ornstein-Uhlenbeck process for correlated noise.
All distribution classes extend Variation (from dm_control.composer.variation.base), which is an abstract callable with the signature __call__(initial_value=None, current_value=None, random_state=None). The base Variation class supports arithmetic operators (+, -, *, /, **, [], unary -) for composing complex variation expressions.
evaluate (in dm_control/composer/variation/variation_values.py):
evaluate(structure, *args, **kwargs)-- recursively traverses an arbitrary nested structure (list, tuple, dict, namedtuple) and replaces each callable with the value returned by calling it with the given arguments. Constants are left unchanged. Usestree.map_structureinternally.
Usage
Create distribution objects and bind them to MJCF element attributes via a variator. Call apply_variations inside the appropriate task lifecycle callback. Use evaluate for one-off evaluation of nested variation structures.
Code Reference
| Attribute | Value |
|---|---|
| Source Location (variators) | dm_control/composer/variation/__init__.py:L34-136
|
| Source Location (distributions) | dm_control/composer/variation/distributions.py:L25-258
|
| Source Location (base) | dm_control/composer/variation/base.py
|
| Source Location (evaluate) | dm_control/composer/variation/variation_values.py
|
| Signature (MJCFVariator.__init__) | MJCFVariator.__init__(self)
|
| Signature (MJCFVariator.bind_attributes) | MJCFVariator.bind_attributes(self, element, **kwargs)
|
| Signature (MJCFVariator.apply_variations) | MJCFVariator.apply_variations(self, random_state)
|
| Signature (PhysicsVariator.__init__) | PhysicsVariator.__init__(self)
|
| Signature (PhysicsVariator.bind_attributes) | PhysicsVariator.bind_attributes(self, element, **kwargs)
|
| Signature (PhysicsVariator.apply_variations) | PhysicsVariator.apply_variations(self, physics, random_state)
|
| Signature (Uniform) | Uniform.__init__(self, low=0.0, high=1.0, single_sample=False)
|
| Signature (Normal) | Normal.__init__(self, loc=0.0, scale=1.0, single_sample=False)
|
| Signature (evaluate) | evaluate(structure, *args, **kwargs)
|
| Import | from dm_control.composer import variation, from dm_control.composer.variation import distributions
|
I/O Contract
Inputs (MJCFVariator.bind_attributes)
| Name | Type | Description |
|---|---|---|
element |
mjcf.Element |
The MJCF element whose attributes will be varied |
**kwargs |
Variation or value | Keyword arguments mapping attribute names to variation callables or fixed values |
Inputs (MJCFVariator.apply_variations)
| Name | Type | Description |
|---|---|---|
random_state |
np.random.RandomState |
Seeded random number generator |
Inputs (PhysicsVariator.apply_variations)
| Name | Type | Description |
|---|---|---|
physics |
mjcf.Physics |
The compiled physics instance |
random_state |
np.random.RandomState |
Seeded random number generator |
Outputs
| Name | Type | Description |
|---|---|---|
| Side effect | Attribute mutation | The bound MJCF attributes or physics bindings are set to new sampled values in-place |
evaluate return |
nested structure | Same structure as input with callables replaced by their return values |
Usage Examples
Randomizing MJCF attributes before compilation
from dm_control.composer import variation
from dm_control.composer.variation import distributions
class RandomizedTask(task_module.Task):
def __init__(self, robot, arena):
self._arena = arena
self._arena.attach(robot)
self._robot = robot
# Create an MJCF variator for pre-compile randomization
self._mjcf_variator = variation.MJCFVariator()
# Randomize the color of all geoms on the robot
for geom in robot.mjcf_model.find_all('geom'):
self._mjcf_variator.bind_attributes(
geom,
rgba=distributions.Uniform(
low=[0, 0, 0, 1], high=[1, 1, 1, 1]))
@property
def root_entity(self):
return self._arena
def initialize_episode_mjcf(self, random_state):
self._mjcf_variator.apply_variations(random_state)
def get_reward(self, physics):
return 0.0
Randomizing physics parameters after compilation
class DynamicsRandomizedTask(task_module.Task):
def __init__(self, robot, arena):
self._arena = arena
self._arena.attach(robot)
self._robot = robot
# Create a physics variator for post-compile randomization
self._physics_variator = variation.PhysicsVariator()
# Randomize friction of all geoms
for geom in robot.mjcf_model.find_all('geom'):
self._physics_variator.bind_attributes(
geom,
friction=distributions.Uniform(
low=[0.5, 0.001, 0.001],
high=[1.5, 0.01, 0.01]))
@property
def root_entity(self):
return self._arena
def initialize_episode(self, physics, random_state):
self._physics_variator.apply_variations(physics, random_state)
def get_reward(self, physics):
return 0.0
Composing variations with arithmetic
from dm_control.composer.variation import distributions
# Mass = initial_mass * Uniform(0.8, 1.2)
# This is a Variation that, when called, samples from Uniform
# and multiplies the result by the initial attribute value.
mass_scale = distributions.Uniform(low=0.8, high=1.2)
# Additive noise: position = initial + Normal(0, 0.01)
position_noise = distributions.Normal(loc=0.0, scale=0.01)
# Evaluate a nested structure of variations
params = {
'mass': mass_scale,
'pos': [distributions.Uniform(-1, 1), distributions.Uniform(-1, 1), 0.5],
}
sampled = variation.evaluate(
params,
initial_value=None,
current_value=None,
random_state=np.random.RandomState(42))
# sampled = {'mass': 0.93..., 'pos': [0.12..., -0.45..., 0.5]}