Skip to content

Feat(randomization): add articulation mass randomization functor#219

Open
yuecideng wants to merge 2 commits intomainfrom
feat/articulation-mass-randomization
Open

Feat(randomization): add articulation mass randomization functor#219
yuecideng wants to merge 2 commits intomainfrom
feat/articulation-mass-randomization

Conversation

@yuecideng
Copy link
Copy Markdown
Contributor

Description

This PR adds a randomize_articulation_mass event functor that randomizes articulation link masses within a specified range. It also adds set_mass/get_mass methods to the Articulation class for batched mass management.

Key features:

  • Regex-based link selection: The link_names parameter accepts a regex pattern (str), a list of patterns (list[str]), or None (all links)
  • Absolute and relative modes: Supports both absolute mass randomization and relative (additive offset from current mass)
  • Batched API: New Articulation.set_mass(mass, link_names, env_ids) and Articulation.get_mass(link_names, env_ids) methods provide a clean high-level interface

Dependencies: None

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which improves an existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (existing functionality will not work without user modification)
  • Documentation update

Checklist

  • I have run the black . command to format the code base.
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • Dependencies have been updated, if applicable.

🤖 Generated with Claude Code

Add `randomize_articulation_mass` event functor that randomizes
articulation link masses with regex-based link selection via the
`link_names` parameter. Also adds `set_mass`/`get_mass` methods
to the Articulation class for high-level mass management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 5, 2026 16:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an event functor to randomize per-link masses on articulations, along with a batched Articulation.get_mass() / Articulation.set_mass() API to support that randomization (plus docs/tests updates).

Changes:

  • Add randomize_articulation_mass functor with regex-based link selection and absolute/relative modes.
  • Add batched mass getters/setters on Articulation for per-link mass management.
  • Extend event functor tests and documentation to cover the new mass randomization.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/gym/envs/managers/test_event_functors.py Adds mock per-link mass support and tests for randomize_articulation_mass.
embodichain/lab/sim/objects/articulation.py Introduces Articulation.set_mass() / Articulation.get_mass() batched APIs.
embodichain/lab/sim/cfg.py Clarifies articulation link mass/density behavior in config docstring.
embodichain/lab/gym/envs/managers/randomization/physics.py Adds randomize_articulation_mass and related imports.
docs/source/overview/gym/event_functors.md Documents the new event functor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +150 to +157
lower=mass_range[0], upper=mass_range[1], size=(num_instance, num_links)
)

if relative:
# Get current mass from the articulation
current_masses = articulation.get_mass(
link_names=matched_link_names, env_ids=env_ids
)
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample_uniform is called without a device, so sampled_masses will be on CPU. In relative=True mode you then add it to current_masses returned by articulation.get_mass() (allocated on articulation.device), which will raise a device mismatch when the sim runs on CUDA. Pass an explicit device (e.g., articulation.device/env.device) to sample_uniform, and ensure both tensors are on the same device before adding.

Suggested change
lower=mass_range[0], upper=mass_range[1], size=(num_instance, num_links)
)
if relative:
# Get current mass from the articulation
current_masses = articulation.get_mass(
link_names=matched_link_names, env_ids=env_ids
)
lower=mass_range[0],
upper=mass_range[1],
size=(num_instance, num_links),
device=articulation.device,
)
if relative:
# Get current mass from the articulation
current_masses = articulation.get_mass(
link_names=matched_link_names, env_ids=env_ids
).to(articulation.device)

Copilot uses AI. Check for mistakes.
Comment on lines +22 to 26
from embodichain.lab.sim.objects import Articulation, RigidObject, Robot
from embodichain.lab.gym.envs.managers.cfg import SceneEntityCfg
from embodichain.utils.math import sample_uniform
from embodichain.utils.string import resolve_matching_names
from embodichain.utils import logger
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robot is imported but unused in this module. Please remove it to avoid unused-import warnings and keep the imports minimal.

Copilot uses AI. Check for mistakes.

for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
self._entities[env_idx].set_mass(name, mass[i, j].item())
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mass() calls self._entities[env_idx].set_mass(name, ...), but the rest of the codebase interacts with masses through get_physical_body(...).get_mass() / RigidBody.set_mass(...). Unless the underlying articulation entity exposes set_mass, this will raise at runtime. Consider using self._entities[env_idx].get_physical_body(name).set_mass(...) for symmetry with get_mass() and with RigidObject.set_mass().

Suggested change
self._entities[env_idx].set_mass(name, mass[i, j].item())
self._entities[env_idx].get_physical_body(name).set_mass(
mass[i, j].item()
)

Copilot uses AI. Check for mistakes.
Comment on lines +1246 to +1249
if len(local_env_ids) != len(mass):
logger.log_error(
f"Length of env_ids {len(local_env_ids)} does not match mass length {len(mass)}."
)
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mass() validates only the first dimension (env count) but not that mass is 2D and that mass.shape[1] == len(link_names). If callers pass a mismatched tensor, this will fail later with an IndexError (or silently set wrong values). Add an explicit shape check and raise via logger.log_error/ValueError before looping.

Suggested change
if len(local_env_ids) != len(mass):
logger.log_error(
f"Length of env_ids {len(local_env_ids)} does not match mass length {len(mass)}."
)
expected_shape = (len(local_env_ids), len(link_names))
if mass.ndim != 2 or mass.shape[0] != expected_shape[0] or mass.shape[1] != expected_shape[1]:
msg = (
f"Mass tensor must have shape {expected_shape}, but got {tuple(mass.shape)}."
)
logger.log_error(msg)
raise ValueError(msg)

Copilot uses AI. Check for mistakes.
Comment on lines +1257 to +1259
for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
self._entities[env_idx].set_mass(name, mass[i, j].item())
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mass() uses mass[i, j].item() inside a nested loop. On CUDA tensors this causes a device sync per element and can become very slow for many envs/links. Convert the entire tensor once (e.g., mass.cpu().numpy()), then index into that in the loop (similar to RigidObject.set_mass).

Suggested change
for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
self._entities[env_idx].set_mass(name, mass[i, j].item())
mass_np = mass.cpu().numpy()
for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
self._entities[env_idx].set_mass(name, mass_np[i, j])

Copilot uses AI. Check for mistakes.
Comment on lines +1286 to +1296
mass_tensor = torch.zeros(
(len(local_env_ids), len(link_names)),
dtype=torch.float32,
device=self.device,
)
for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
mass_tensor[i, j] = (
self._entities[env_idx].get_physical_body(name).get_mass()
)
return mass_tensor
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_mass() fills a tensor element-by-element in Python. If self.device is CUDA, this results in many small host->device writes. Prefer accumulating masses in a Python list / NumPy array on CPU and then torch.as_tensor(..., device=self.device) once at the end (as done in RigidObject.get_mass).

Suggested change
mass_tensor = torch.zeros(
(len(local_env_ids), len(link_names)),
dtype=torch.float32,
device=self.device,
)
for i, env_idx in enumerate(local_env_ids):
for j, name in enumerate(link_names):
mass_tensor[i, j] = (
self._entities[env_idx].get_physical_body(name).get_mass()
)
return mass_tensor
mass_values = []
for env_idx in local_env_ids:
env_mass_values = []
for name in link_names:
env_mass_values.append(
self._entities[env_idx].get_physical_body(name).get_mass()
)
mass_values.append(env_mass_values)
return torch.as_tensor(
mass_values,
dtype=torch.float32,
device=self.device,
)

Copilot uses AI. Check for mistakes.
@yuecideng yuecideng added gym robot learning env and its related features object Simulation object assets labels Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gym robot learning env and its related features object Simulation object assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants