Helper Functions for Accessing Servo Joint IDs and Positions in Microduck RL
Microduck RL provides four canonical helper functions in src/mjlab_microduck/tasks/mdp.py—_servo_joint_ids(), _servo_joint_pos(), _servo_joint_vel(), and _servo_default_joint_pos()—that filter and cache servo joint indices to isolate 14 actuated degrees of freedom from passive joints.
The Microduck RL reinforcement learning framework isolates controllable servo joints from unactuated passive mechanisms using dedicated utility functions. These helpers ensure that policies and reward calculations always reference the correct 14-DOF servo layout, regardless of whether the underlying MJCF model contains additional passive joints like backlash hinges or wheels. All joint data access in the repository flows through these centralized utilities defined in the MDP task module.
The Four Canonical Servo Joint Helpers
The mdp.py module defines the canonical interface for servo joint access. According to the Microduck RL source code at lines 26–55, these functions guarantee a consistent 14-servo layout even when the MJCF model contains extra passive joints.
_servo_joint_ids(env, asset)
This function returns the entity-local indices of all non-passive joints. It uses the regular expression r"^(?!passive_).*" to filter out any joint whose name starts with passive_, ensuring that only actuated servos are selected.
To optimize performance, the result is cached on the environment object under env.__dict__['_servo_joint_ids_cache']. Subsequent calls retrieve cached indices in O(1) time, eliminating the overhead of repeated regex searches via asset.find_joints.
_servo_joint_pos(env, asset)
Returns a tensor of joint positions for the servo joints only. This function calls _servo_joint_ids() to obtain the correct indices, then indexes asset.data.joint_pos to extract the position data for the 14 active DOFs.
_servo_joint_vel(env, asset)
Provides a tensor of joint velocities for the servo joints only. The implementation mirrors _servo_joint_pos() but indexes asset.data.joint_vel instead, ensuring velocity calculations exclude unactuated passive mechanisms.
_servo_default_joint_pos(env, asset)
Retrieves the default (rest) joint positions for the servo joints by indexing asset.data.default_joint_pos using the cached servo joint IDs. This is essential for computing pose deviations and reset states.
Implementation Details: Caching and Passive Joint Filtering
The architectural design of these helpers addresses two critical requirements in the Microduck RL training pipeline.
Regex-Based Filtering
The robot model contains extra joints that are unactuated (e.g., wheels, backlash hinges, jaw linkages). Their indices interleave with the servo joints, so directly using raw joint arrays would select the wrong DOFs. The negative lookahead regex r"^(?!passive_).*" explicitly excludes any joint prefixed with passive_`, preserving the invariant that policies receive a 61-dimensional observation vector with fixed joint ordering.
Cache invalidation Strategy
Computing the servo joint list requires a regex search through the asset's joint names. Since every environment step may need joint data, the lookup is performed once per asset and stored in env.__dict__['_servo_joint_ids_cache']. This design keeps per-step overhead minimal while maintaining correctness across episode resets.
Practical Usage Examples
Below are typical patterns found throughout the repository for accessing servo joint data.
Computing Joint Accelerations
def joint_accelerations_l2(env, asset_cfg=SceneEntityCfg("robot")):
asset = env.scene[asset_cfg.name]
# Servo joint positions and velocities only
q = _servo_joint_pos(env, asset) # shape (N, 14)
v = _servo_joint_vel(env, asset) # shape (N, 14)
# Compute accelerations, penalties, etc.
Retrieving Default Rest Pose
def get_rest_pose(env):
asset = env.scene["robot"]
rest_q = _servo_default_joint_pos(env, asset) # shape (N, 14)
return rest_q
Exporting ONNX Metadata
def _get_base_metadata_no_passive(env, run_path):
robot = env.scene["robot"]
joint_action = env.action_manager.get_term("joint_pos")
# Joint IDs for the servo joints only
servo_ids = _servo_joint_ids(env, robot) # list of 14 ints
# Use servo_ids to slice actuator properties
Integration with Training Pipeline
These helpers centralize joint access across multiple modules, making the code robust to future model changes.
Reward Function Integration
Many reward terms rely on these utilities to slice joint tensors correctly. Functions such as standing_composite_score, leg_action_rate_l2, and neck_action_rate_l2 all call _servo_joint_ids() to ensure they compute penalties only on actuated joints, ignoring passive backlash or wheel joints.
Environment Configuration
The backlash.py module wraps base environment configurations to add backlash variants while relying on the same joint-ID helpers to maintain index consistency. Similarly, microduck_velocity_env_cfg.py uses these helpers indirectly through reward term configurations.
Key Files
src/mjlab_microduck/tasks/mdp.py: Central module defining servo-joint helper functions and reward utilities.src/mjlab_microduck/tasks/backlash.py: Wraps environments with backlash variants using consistent joint indexing.src/mjlab_microduck/tasks/microduck_velocity_env_cfg.py: Task configuration demonstrating indirect usage via reward terms.
Summary
- Four canonical functions—
_servo_joint_ids(),_servo_joint_pos(),_servo_joint_vel(), and_servo_default_joint_pos()—provide the sole interface for accessing the 14 actuated servo joints in Microduck RL. - Regex filtering with
r"^(?!passive_).*"isolates actuated joints from passive mechanisms like wheels and backlash hinges. - O(1) caching via
env.__dict__['_servo_joint_ids_cache']eliminates repeated joint lookups during training steps. - Repository-wide usage ensures reward functions, observation generators, and ONNX export tools operate on a stable, model-agnostic joint layout.
Frequently Asked Questions
Why does Microduck RL need helper functions for servo joint access?
The underlying MJCF robot model contains passive joints—such as backlash hinges and wheel mechanisms—that interleave with actuated servo joints in the joint array. Direct indexing would select wrong DOFs and break the 14-servo layout invariant. The helper functions filter these out using regex patterns, ensuring policies and rewards always reference the correct actuated joints.
How does the caching mechanism work for servo joint IDs?
The first call to _servo_joint_ids() performs an expensive regex search via asset.find_joints and stores the result in env.__dict__['_servo_joint_ids_cache']. Subsequent calls retrieve this cached list instantly, providing O(1) access during high-frequency training steps.
What is the difference between servo joints and passive joints in Microduck RL?
Servo joints are the 14 actuated degrees of freedom controlled by the policy. Passive joints are unactuated mechanical elements (e.g., backlash hinges, jaw linkages, wheels) included in the physics model for realism but not driven by actuators. The helper functions explicitly exclude any joint with names starting with passive_.
Which reward functions use these servo joint helpers?
Reward terms including standing_composite_score, leg_action_rate_l2, and neck_action_rate_l2 all call _servo_joint_ids() to slice joint position and velocity tensors. This centralization ensures penalties apply only to controllable servos, not passive mechanisms.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →