|
| 1 | +# MDP terms reference |
| 2 | + |
| 3 | +`genelab.mdp` is a library of plain Python callables that plug straight into |
| 4 | +the seven managers. Every function follows the same signature shape — `(env, |
| 5 | +**params) -> torch.Tensor` for observation / reward / termination, `(env, |
| 6 | +env_ids, **params) -> None` for events, `(env, env_ids, **params) -> Tensor` |
| 7 | +for curriculums — and is referenced by the corresponding `*TermCfg.func` (or |
| 8 | +`class_type`) field. |
| 9 | + |
| 10 | +## Reusable building blocks |
| 11 | + |
| 12 | +Every name listed below is re-exported by `genelab.mdp.__init__`, so |
| 13 | +`from genelab import mdp` is enough to reach them. The full module map: |
| 14 | + |
| 15 | +``` |
| 16 | +genelab.mdp.actions → JointPositionAction(Cfg) |
| 17 | +genelab.mdp.commands → UniformVelocityCommand(Cfg), MotionCommand(Cfg), MotionLoader |
| 18 | +genelab.mdp.observations → 16 observation functions |
| 19 | +genelab.mdp.rewards → 15 reward functions / classes |
| 20 | +genelab.mdp.terminations → 6 termination functions |
| 21 | +genelab.mdp.events → 3 event functions |
| 22 | +genelab.mdp.curriculums → 1 curriculum function |
| 23 | +genelab.mdp.noise → NoiseCfg base + Unoise + Gnoise |
| 24 | +``` |
| 25 | + |
| 26 | +The output-shape column below uses `B = num_envs`, `D = action / joint / |
| 27 | +sensor dimension`, `N = number of bodies or feet`. Reward and termination |
| 28 | +terms always return shape `(B,)`; observation terms return `(B, D)` (or are |
| 29 | +auto-unsqueezed from `(B,)` by `ObservationManager`). |
| 30 | + |
| 31 | +## Actions |
| 32 | + |
| 33 | +| Name | Signature (Cfg fields used) | Behavior | |
| 34 | +|---|---|---| |
| 35 | +| `JointPositionAction` | `JointPositionActionCfg(joint_names, scale, use_default_offset, asset_name)` | `target = default + scale * raw_action`. Targets are dispatched via `Articulation.write_joint_targets_partial`, which routes each joint to its actuator group's declared channel (position for implicit PD, force for `IdealPDActuator` / `DCMotorActuator`). `scale=None` (default) inherits per-joint scale from `Articulation.action_scale_tensor`; a `float` or `dict[str, float]` overrides it. Patterns are regex; unmatched patterns raise. | |
| 36 | + |
| 37 | +```python |
| 38 | +from genelab.mdp.actions.joint_position import JointPositionActionCfg |
| 39 | + |
| 40 | +actions_cfg = { |
| 41 | + "panda_arm": JointPositionActionCfg( |
| 42 | + asset_name="robot", |
| 43 | + joint_names=(r"^joint[1-7]$",), |
| 44 | + use_default_offset=True, |
| 45 | + ), |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +## Commands |
| 50 | + |
| 51 | +| Name | Signature (Cfg fields used) | Behavior | |
| 52 | +|---|---|---| |
| 53 | +| `UniformVelocityCommand` | `UniformVelocityCommandCfg(ranges, rel_standing_envs, heading_command, heading_control_stiffness, resampling_time_range, asset_name)` | Body-frame `[lin_vel_x, lin_vel_y, ang_vel_z]` per env, resampled uniformly from `ranges` every `resampling_time_range` seconds. With `heading_command=True` (default), `ang_vel_z` is rewritten each step to drive the body toward a sampled heading. `rel_standing_envs` zeroes a fraction of resampled commands so the policy also sees a "stand still" target. `command` shape `(B, 3)`. | |
| 54 | +| `MotionCommand` | `MotionCommandCfg(motion_file, anchor_body_name, body_names, pose_range, velocity_range, sampling_mode, ...)` | Drives the env toward a recorded motion clip frame-by-frame. Reads the NPZ schema produced by mjlab's `csv_to_npz`. Exposes anchor + multi-body reference and current pose / velocity for the relative-pose reward functions in the next section. `sampling_mode="start"` always begins at frame 0; `"uniform"` (default) samples a random frame. | |
| 55 | +| `MotionLoader` | `MotionLoader(motion_file, body_indexes, device)` | Helper class consumed by `MotionCommand`. Loads the NPZ into device tensors and slices `body_pos_w / body_quat_w / body_lin_vel_w / body_ang_vel_w` down to the bodies the command tracks. Direct use is rare — instantiate `MotionCommandCfg` instead. | |
| 56 | + |
| 57 | +```python |
| 58 | +from genelab.mdp.commands.velocity_command import UniformVelocityCommandCfg |
| 59 | + |
| 60 | +commands_cfg = { |
| 61 | + "base_velocity": UniformVelocityCommandCfg( |
| 62 | + asset_name="robot", |
| 63 | + resampling_time_range=(10.0, 10.0), |
| 64 | + ranges=UniformVelocityCommandCfg.Ranges( |
| 65 | + lin_vel_x=(-1.0, 1.0), lin_vel_y=(-0.5, 0.5), ang_vel_z=(-1.0, 1.0), |
| 66 | + ), |
| 67 | + heading_command=True, |
| 68 | + ), |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +## Observations |
| 73 | + |
| 74 | +All listed functions take `env` plus the noted kwargs and return a tensor of |
| 75 | +shape `(B, D)` or `(B,)` (auto-unsqueezed to `(B, 1)` by the manager). |
| 76 | + |
| 77 | +| Name | Params | Shape | Source | |
| 78 | +|---|---|---|---| |
| 79 | +| `base_lin_vel` | — | `(B, 3)` | Body-frame floating-base linear velocity. | |
| 80 | +| `base_ang_vel` | — | `(B, 3)` | Body-frame floating-base angular velocity. | |
| 81 | +| `projected_gravity` | — | `(B, 3)` | World gravity projected into the body frame (IMU-orientation proxy). | |
| 82 | +| `joint_pos_rel` | — | `(B, num_dofs)` | Joint positions minus the default pose. | |
| 83 | +| `joint_vel_rel` | — | `(B, num_dofs)` | Joint velocities (default is zero — same as raw `joint_vel`). | |
| 84 | +| `last_action` | — | `(B, total_action_dim)` | The action tensor the manager processed last step. | |
| 85 | +| `generated_commands` | `command_name` | `(B, command_dim)` | `env.command_manager.get_command(command_name)`. | |
| 86 | +| `sensor_data` | `sensor_name` | varies | Per-step cached tensor on `env.sensors[sensor_name]`. Use this for IMU, FrameTransformer, ray-cast outputs that are already tensors. | |
| 87 | +| `foot_air_time` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet)` | Current per-foot air time (zero while in contact). | |
| 88 | +| `foot_contact` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet)` | Binary contact mask as float. | |
| 89 | +| `foot_contact_forces` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet * 3)` | Per-foot contact force, compressed via `sign(f) * log1p(|f|)` and flattened. | |
| 90 | +| `height_scan` | `sensor_name` (must be a `TerrainHeightSensor`) | `(B, num_rays)` | Per-ray heights above the terrain. | |
| 91 | +| `motion_anchor_pos_b` | `command_name` (must be a `MotionCommand`) | `(B, 3)` | Reference anchor position expressed in the robot's anchor frame. | |
| 92 | +| `motion_anchor_ori_b` | `command_name` | `(B, 6)` | Reference 6D orientation (first two columns of the rotation matrix). | |
| 93 | +| `robot_body_pos_b` | `command_name` | `(B, N_bodies * 3)` | Per-body positions in the robot's anchor frame (privileged critic obs). | |
| 94 | +| `robot_body_ori_b` | `command_name` | `(B, N_bodies * 6)` | Per-body 6D orientations in the robot's anchor frame. | |
| 95 | + |
| 96 | +```python |
| 97 | +from genelab.managers import ObservationGroupCfg, ObservationTermCfg |
| 98 | +from genelab import mdp |
| 99 | +from genelab.mdp.noise import Unoise |
| 100 | + |
| 101 | +observations_cfg = { |
| 102 | + "policy": ObservationGroupCfg( |
| 103 | + terms={ |
| 104 | + "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, |
| 105 | + noise=Unoise(n_min=-0.1, n_max=0.1)), |
| 106 | + "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel), |
| 107 | + "last_action": ObservationTermCfg(func=mdp.last_action), |
| 108 | + "commands": ObservationTermCfg(func=mdp.generated_commands, |
| 109 | + params={"command_name": "base_velocity"}), |
| 110 | + }, |
| 111 | + enable_corruption=True, |
| 112 | + ), |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +## Rewards |
| 117 | + |
| 118 | +All reward functions return shape `(B,)`. `RewardManager` multiplies by |
| 119 | +`RewardTermCfg.weight` and (when `scale_rewards_by_dt=True`) by `dt`. Negative |
| 120 | +weights turn a "deviation" reward into a penalty. |
| 121 | + |
| 122 | +| Name | Params | Behavior | |
| 123 | +|---|---|---| |
| 124 | +| `track_linear_velocity_xy_exp` | `command_name`, `std=0.5` | `exp(-||cmd_xy − vel_xy||² / std²)`. | |
| 125 | +| `track_angular_velocity_z_exp` | `command_name`, `std=0.5` | `exp(-(cmd_z − vel_z)² / std²)`. | |
| 126 | +| `action_rate_l2` | — | `Σ_d (action_d − prev_action_d)²` — penalize action jitter. | |
| 127 | +| `joint_acc_l2` | — | **Placeholder** (currently returns zeros; awaits accel buffer). | |
| 128 | +| `flat_orientation_l2` | — | `Σ (projected_gravity_xy)²` — penalize tilt. | |
| 129 | +| `upright_exp` | `std=0.45` | `exp(-||projected_gravity_xy||² / std²)` — positive reward for upright base. | |
| 130 | +| `variable_posture` | `command_name`, `std_standing` / `std_walking` / `std_running` (regex→float dicts), `default_std`, `walking_threshold`, `running_threshold` | Speed-dependent posture reward: `exp(-mean((joint_pos − default)² / std²))`, with std chosen per env from the command magnitude. Class-style term — instantiated once at construct, then `__call__` per step. | |
| 131 | +| `joint_pos_limits` | — | L2 of joint-position excursion past ±π. | |
| 132 | +| `feet_air_time` | `threshold=0.4` | **Stub** — reward proportional to mean foot-link height above ground, clamped at `threshold`. | |
| 133 | +| `motion_global_anchor_position_error_exp` | `command_name`, `std` | `exp(-||p_ref − p_robot||² / std²)` on the anchor body in world frame. | |
| 134 | +| `motion_global_anchor_orientation_error_exp` | `command_name`, `std` | Geodesic rotation error on the anchor body, mapped through a Gaussian kernel. | |
| 135 | +| `motion_relative_body_position_error_exp` | `command_name`, `std`, `body_names=None` | Multi-body L2 position error against anchor-aligned reference frames. `body_names=None` includes every tracked body. | |
| 136 | +| `motion_relative_body_orientation_error_exp` | `command_name`, `std`, `body_names=None` | Multi-body geodesic rotation error against anchor-aligned references. | |
| 137 | +| `motion_global_body_linear_velocity_error_exp` | `command_name`, `std`, `body_names=None` | L2 world-frame linear-velocity error across the tracked bodies. | |
| 138 | +| `motion_global_body_angular_velocity_error_exp` | `command_name`, `std`, `body_names=None` | L2 world-frame angular-velocity error across the tracked bodies. | |
| 139 | + |
| 140 | +!!! warning "Placeholder rewards" |
| 141 | + `joint_acc_l2` and `feet_air_time` are stubs awaiting proper accel / |
| 142 | + contact buffers. They compile and emit a tensor of the correct shape but |
| 143 | + the *value* is not the locomotion-grade signal one would normally expect |
| 144 | + from these names. Treat them as `weight=0.0` placeholders until they are |
| 145 | + wired against per-step velocity history (`joint_acc_l2`) and a |
| 146 | + `ContactSensor` (`feet_air_time` — `mdp.foot_air_time` already provides |
| 147 | + the real signal as an observation term). |
| 148 | + |
| 149 | +```python |
| 150 | +from genelab.managers import RewardTermCfg |
| 151 | +from genelab import mdp |
| 152 | + |
| 153 | +rewards_cfg = { |
| 154 | + "track_lin_vel": RewardTermCfg( |
| 155 | + func=mdp.track_linear_velocity_xy_exp, |
| 156 | + weight=1.0, |
| 157 | + params={"command_name": "base_velocity", "std": 0.25}, |
| 158 | + ), |
| 159 | + "track_ang_vel": RewardTermCfg( |
| 160 | + func=mdp.track_angular_velocity_z_exp, |
| 161 | + weight=0.5, |
| 162 | + params={"command_name": "base_velocity", "std": 0.25}, |
| 163 | + ), |
| 164 | + "upright": RewardTermCfg(func=mdp.upright_exp, weight=0.2), |
| 165 | + "action_rate": RewardTermCfg(func=mdp.action_rate_l2, weight=-0.005), |
| 166 | + "flat_ori": RewardTermCfg(func=mdp.flat_orientation_l2, weight=-0.5), |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +## Terminations |
| 171 | + |
| 172 | +All termination functions return shape `(B,)` bool. `TerminationTermCfg.time_out` |
| 173 | +routes the value to the truncation buffer (RSL-RL's "info['time_out']") instead |
| 174 | +of the terminated buffer. |
| 175 | + |
| 176 | +| Name | Params | Behavior | |
| 177 | +|---|---|---| |
| 178 | +| `time_out` | — | `episode_length_buf >= max_episode_length`. Always paired with `time_out=True`. | |
| 179 | +| `bad_orientation` | `limit_angle=math.radians(70.0)` | True when the body z-axis tilts more than `limit_angle` from world up. | |
| 180 | +| `root_height_below` | `min_height` | True when `root_pos.z < min_height`. | |
| 181 | +| `bad_anchor_pos_z_only` | `command_name`, `threshold` | True when the robot anchor z drifts further than `threshold` from the reference clip. | |
| 182 | +| `bad_anchor_ori` | `command_name`, `threshold` | True when the tilt error between robot anchor and reference exceeds `threshold` (gravity-z proxy). | |
| 183 | +| `bad_motion_body_pos_z_only` | `command_name`, `threshold`, `body_names=None` | True when any selected body's vertical position deviates past `threshold`. | |
| 184 | + |
| 185 | +```python |
| 186 | +from genelab.managers import TerminationTermCfg |
| 187 | +from genelab import mdp |
| 188 | + |
| 189 | +terminations_cfg = { |
| 190 | + "time_out": TerminationTermCfg(func=mdp.time_out, time_out=True), |
| 191 | + "fell_over": TerminationTermCfg(func=mdp.bad_orientation, |
| 192 | + params={"limit_angle": 1.0}), |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +## Events |
| 197 | + |
| 198 | +Event functions take an extra `env_ids` argument and return `None`. The |
| 199 | +`EventTermCfg.mode` decides when they fire — `startup` once at construct, |
| 200 | +`reset` on every env reset, `interval` on per-env countdown (requires |
| 201 | +`interval_range_s`). |
| 202 | + |
| 203 | +| Name | Params | Behavior | |
| 204 | +|---|---|---| |
| 205 | +| `reset_root_state_uniform` | `pose_range` (`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)` dict), `velocity_range` (same axes) | Randomize floating-base pose and velocity within the given ranges. Pose offsets are added to `cfg.robot.init_pos`; orientation offsets layer on top of `cfg.robot.init_quat`. | |
| 206 | +| `reset_joints_to_default` | `pos_jitter=0.0`, `vel_jitter=0.0` | Write the default joint pose to the selected envs, optionally with uniform ±jitter on position and velocity. | |
| 207 | +| `push_by_setting_velocity` | `velocity_range` (`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)`) | Overwrite the base linear and angular velocity. Combined with `mode="interval"`, this is the canonical "random push" disturbance. | |
| 208 | + |
| 209 | +```python |
| 210 | +from genelab.managers import EventTermCfg |
| 211 | +from genelab import mdp |
| 212 | + |
| 213 | +events_cfg = { |
| 214 | + "reset_root": EventTermCfg( |
| 215 | + mode="reset", |
| 216 | + func=mdp.reset_root_state_uniform, |
| 217 | + params={"pose_range": {"yaw": (-3.14, 3.14)}, |
| 218 | + "velocity_range": {"x": (-0.1, 0.1)}}, |
| 219 | + ), |
| 220 | + "push_robot": EventTermCfg( |
| 221 | + mode="interval", |
| 222 | + func=mdp.push_by_setting_velocity, |
| 223 | + interval_range_s=(8.0, 12.0), |
| 224 | + params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, |
| 225 | + ), |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +## Curriculums |
| 230 | + |
| 231 | +| Name | Params | Behavior | |
| 232 | +|---|---|---| |
| 233 | +| `terrain_levels_vel` | `distance_threshold`, `demote_ratio=0.5` | Promote / demote each env's `TerrainImporter.terrain_levels` index by how far it walked from spawn. Envs that travelled more than `distance_threshold` move up; envs that travelled less than `distance_threshold * demote_ratio` move down. Levels then drive a fresh spawn-origin lookup, and the curriculum writes the new root pose into the sim. No-op when `env.scene.terrain is None`. Returns the per-env mean level so the manager logs `Curriculum/<term-name>`. | |
| 234 | + |
| 235 | +```python |
| 236 | +from genelab.managers import CurriculumTermCfg |
| 237 | +from genelab.mdp.curriculums import terrain_levels_vel |
| 238 | + |
| 239 | +curriculum_cfg = { |
| 240 | + "terrain_levels": CurriculumTermCfg( |
| 241 | + func=terrain_levels_vel, |
| 242 | + params={"distance_threshold": 5.0, "demote_ratio": 0.5}, |
| 243 | + ), |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +## Noise |
| 248 | + |
| 249 | +`NoiseCfg` is the abstract base; concrete subclasses plug into |
| 250 | +`ObservationTermCfg.noise` and only fire when the enclosing |
| 251 | +`ObservationGroupCfg.enable_corruption=True`. |
| 252 | + |
| 253 | +| Name | Fields | Behavior | |
| 254 | +|---|---|---| |
| 255 | +| `NoiseCfg` | — | Abstract base; subclass and implement `apply(data) -> Tensor`. | |
| 256 | +| `Unoise` | `n_min=-1.0`, `n_max=1.0` | Uniform additive noise sampled in `[n_min, n_max]`. | |
| 257 | +| `Gnoise` | `mean=0.0`, `std=1.0` | Gaussian additive noise `N(mean, std²)`. | |
| 258 | + |
| 259 | +```python |
| 260 | +from genelab.managers import ObservationGroupCfg, ObservationTermCfg |
| 261 | +from genelab.mdp.noise import Gnoise, Unoise |
| 262 | +from genelab import mdp |
| 263 | + |
| 264 | +observations_cfg = { |
| 265 | + "policy": ObservationGroupCfg( |
| 266 | + enable_corruption=True, |
| 267 | + terms={ |
| 268 | + "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, |
| 269 | + noise=Unoise(n_min=-0.1, n_max=0.1)), |
| 270 | + "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel, |
| 271 | + noise=Gnoise(std=0.01)), |
| 272 | + }, |
| 273 | + ), |
| 274 | +} |
| 275 | +``` |
| 276 | + |
| 277 | +## See also |
| 278 | + |
| 279 | +- [Managers and MDP terms](managers.md) |
| 280 | +- [Sensors](sensors.md) |
| 281 | +- [API Reference](../api/reference.md) |
0 commit comments