upload checkpoints

This commit is contained in:
xtkuang 2026-07-13 10:57:13 +08:00
parent b989c02b1b
commit a80c681381
309 changed files with 148230 additions and 3 deletions

2
.gitignore vendored
View File

@ -3,7 +3,5 @@
build/
*.egg-info/
*__pycache__/
*logs/
*outputs/
dataset/
models/

View File

@ -0,0 +1,224 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..553a153 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -3,3 +3,4 @@ from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..1b7cbbf 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -18,6 +18,12 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +96,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +106,34 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv, command_name: str, threshold: float, sensor_cfg: SceneEntityCfg
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -141,7 +175,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +199,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +229,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +243,17 @@ def feet_contact_fixed(
return reward
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -293,7 +340,7 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
@@ -412,7 +459,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 1500
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_walk_v0
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: false
load_run: .*
load_checkpoint: model_.*.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,227 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..553a153 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -3,3 +3,4 @@ from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..1b7cbbf 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -18,6 +18,12 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +96,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +106,34 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv, command_name: str, threshold: float, sensor_cfg: SceneEntityCfg
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -141,7 +175,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +199,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +229,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +243,17 @@ def feet_contact_fixed(
return reward
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -293,7 +340,7 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
@@ -412,7 +459,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 2000
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_collision_v1
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: false
load_run: .*
load_checkpoint: model_.*.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,506 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/observations.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..553a153 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -3,3 +3,4 @@ from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/observations.py b/source/engineai_lab/tasks/velocity/mdp/observations.py
index 0881dba..9dc20ff 100644
--- a/source/engineai_lab/tasks/velocity/mdp/observations.py
+++ b/source/engineai_lab/tasks/velocity/mdp/observations.py
@@ -3,12 +3,30 @@ from __future__ import annotations
import torch
from typing import TYPE_CHECKING
-from isaaclab.utils.math import quat_apply_inverse
+from isaaclab.managers import SceneEntityCfg
+from isaaclab.utils.math import euler_xyz_from_quat, quat_apply_inverse, quat_from_euler_xyz
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
"""Base linear velocity expressed in the base frame."""
asset = env.scene["robot"]
@@ -18,3 +36,22 @@ def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
# fallback: rotate world velocity into base frame
return quat_apply_inverse(asset.data.root_quat_w, asset.data.root_lin_vel_w)
+
+def body_ang_vel_yaw_frame(
+ env: ManagerBasedEnv,
+ asset_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Body angular velocity expressed in a yaw-aligned frame for that body."""
+ body_id = _single_body_id(asset_cfg, "body_ang_vel_yaw_frame")
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_ang_vel_w = asset.data.body_ang_vel_w[:, body_id, :]
+ return quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_ang_vel_w)
+
+
+def body_projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor:
+ """Gravity projection in a configured body's local frame."""
+ body_id = _single_body_id(asset_cfg, "body_projected_gravity")
+ asset = env.scene[asset_cfg.name]
+ return quat_apply_inverse(asset.data.body_quat_w[:, body_id, :], asset.data.GRAVITY_VEC_W)
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..4ec7a99 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -18,6 +18,34 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
+def _heading_yaw_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ return wrap_to_pi(yaw - heading_yaw_offset)
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +118,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +128,34 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv, command_name: str, threshold: float, sensor_cfg: SceneEntityCfg
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -132,6 +188,53 @@ def track_ang_vel_z_world_exp(
rew_abs = torch.exp(-ang_vel_error_abs * sigma)
return torch.where(stand_command, rew_abs, rew_square)
+
+def track_lin_vel_xy_yaw_frame_exp_body(
+ env,
+ sigma: float,
+ command_name: str,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Track planar velocity of a configured body in its yaw-aligned frame."""
+ body_id = _single_body_id(asset_cfg, "track_lin_vel_xy_yaw_frame_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_lin_vel_w = asset.data.body_lin_vel_w[:, body_id, :]
+ vel_yaw = quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_lin_vel_w)
+ lin_vel_error_square = torch.sum(torch.square(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ lin_vel_error_abs = torch.sum(torch.abs(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ rew_square = torch.exp(-lin_vel_error_square * sigma)
+ rew_abs = torch.exp(-lin_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_body(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track world-frame yaw angular velocity of a configured body."""
+ body_id = _single_body_id(asset_cfg, "track_ang_vel_z_world_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ ang_vel_error_square = torch.square(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ ang_vel_error_abs = torch.abs(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
def feet_stumble(
env, sensor_cfg: SceneEntityCfg, tangential_threshold: float = 2.0, normal_threshold: float = 1.0
) -> torch.Tensor:
@@ -141,7 +244,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +268,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +298,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +312,17 @@ def feet_contact_fixed(
return reward
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -253,6 +369,43 @@ def feet_position(env,
return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+def feet_position_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ desired_foot_positions: tuple[tuple[float, float, float], ...],
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 3.0,
+) -> torch.Tensor:
+ """Reward standing foot positions relative to a configured reference body."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_position_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = asset.data.body_pos_w[:, asset_cfg.body_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_pos_w.shape
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat_per_foot = heading_quat.unsqueeze(1).expand(-1, num_feet, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat_per_foot, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, num_feet, 3)
+
+ desired = torch.tensor(desired_foot_positions, dtype=feet_pos_heading.dtype, device=feet_pos_heading.device)
+ if desired.shape != (num_feet, 3):
+ raise ValueError(f"desired_foot_positions must have shape ({num_feet}, 3), got {tuple(desired.shape)}.")
+ position_error = torch.sum(torch.abs(feet_pos_heading - desired.unsqueeze(0)), dim=(1, 2))
+ reward_stand = torch.exp(-position_error * scale)
+ return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+
+
def feet_regulation(
env,
asset_cfg: SceneEntityCfg,
@@ -293,7 +446,7 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
@@ -350,6 +503,20 @@ def base_height_tracking(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"
height_error = torch.abs(asset.data.root_pos_w[:, 2] - target_height)
return torch.exp(-height_error * 30.0)
+
+def body_height_tracking(
+ env,
+ asset_cfg: SceneEntityCfg,
+ target_height: float,
+ scale: float = 30.0,
+) -> torch.Tensor:
+ """Reward keeping a configured body height near a target height."""
+ body_id = _single_body_id(asset_cfg, "body_height_tracking")
+ asset = env.scene[asset_cfg.name]
+ height_error = torch.abs(asset.data.body_pos_w[:, body_id, 2] - target_height)
+ return torch.exp(-height_error * scale)
+
+
def energy_cost(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize energy consumption approximated by the sum of squared joint torques."""
asset = env.scene[asset_cfg.name]
@@ -385,6 +552,41 @@ def feet_orientation(env, asset_cfg: SceneEntityCfg, command_name: str, stand_th
return torch.exp(-rew * 2.0)
+def feet_orientation_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 2.0,
+) -> torch.Tensor:
+ """Reward foot orientation relative to a configured reference body's heading."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_orientation_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ yaw_command = torch.abs(commands[:, 2]) > stand_threshold
+
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ feet_quat = asset.data.body_quat_w[:, asset_cfg.body_ids, :]
+ reference_quat = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_quat.shape
+ feet_flat = feet_quat.reshape(-1, 4)
+ roll, pitch, yaw = euler_xyz_from_quat(feet_flat)
+ roll = roll.reshape(num_envs, num_feet)
+ pitch = pitch.reshape(num_envs, num_feet)
+ yaw = yaw.reshape(num_envs, num_feet)
+
+ reference_yaw = _heading_yaw_with_offset(reference_quat, heading_yaw_offset)
+ feet_roll_pitch_error = torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1)
+ feet_yaw_error = torch.abs(wrap_to_pi(yaw - reference_yaw.unsqueeze(1)))
+
+ rew = torch.sum(feet_roll_pitch_error + feet_yaw_error, dim=1)
+ rew[yaw_command] = torch.sum(feet_roll_pitch_error[yaw_command], dim=1)
+ return torch.exp(-rew * scale)
+
+
def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Reward keeping the base roll/pitch near zero."""
asset = env.scene[asset_cfg.name]
@@ -392,6 +594,35 @@ def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -
base_euler = torch.stack((roll, pitch, yaw), dim=-1)
return torch.exp(-torch.sum(torch.abs(base_euler[:, :2]), dim=-1) * 10.0)
+
+def body_orientation(env, asset_cfg: SceneEntityCfg, scale: float = 10.0) -> torch.Tensor:
+ """Reward keeping a configured body's roll/pitch near zero."""
+ body_id = _single_body_id(asset_cfg, "body_orientation")
+ asset = env.scene[asset_cfg.name]
+ roll, pitch, yaw = euler_xyz_from_quat(asset.data.body_quat_w[:, body_id, :])
+ return torch.exp(-torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1) * scale)
+
+
+def body_yaw_alignment(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+ reference_heading_yaw_offset: float = 0.0,
+ scale: float = 4.0,
+) -> torch.Tensor:
+ """Reward keeping one body's heading aligned with another body's heading."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_alignment")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_alignment")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ body_yaw = _heading_yaw_with_offset(asset.data.body_quat_w[:, body_id, :], heading_yaw_offset)
+ reference_yaw = _heading_yaw_with_offset(
+ reference_asset.data.body_quat_w[:, reference_body_id, :], reference_heading_yaw_offset
+ )
+ return torch.exp(-torch.abs(wrap_to_pi(body_yaw - reference_yaw)) * scale)
+
+
def reward_waist_pos(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
@@ -412,7 +643,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 1500
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_pelvis_base_v1
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: false
load_run: .*
load_checkpoint: model_.*.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,652 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/observations.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..553a153 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -3,3 +3,4 @@ from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/observations.py b/source/engineai_lab/tasks/velocity/mdp/observations.py
index 0881dba..9dc20ff 100644
--- a/source/engineai_lab/tasks/velocity/mdp/observations.py
+++ b/source/engineai_lab/tasks/velocity/mdp/observations.py
@@ -3,12 +3,30 @@ from __future__ import annotations
import torch
from typing import TYPE_CHECKING
-from isaaclab.utils.math import quat_apply_inverse
+from isaaclab.managers import SceneEntityCfg
+from isaaclab.utils.math import euler_xyz_from_quat, quat_apply_inverse, quat_from_euler_xyz
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
"""Base linear velocity expressed in the base frame."""
asset = env.scene["robot"]
@@ -18,3 +36,22 @@ def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
# fallback: rotate world velocity into base frame
return quat_apply_inverse(asset.data.root_quat_w, asset.data.root_lin_vel_w)
+
+def body_ang_vel_yaw_frame(
+ env: ManagerBasedEnv,
+ asset_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Body angular velocity expressed in a yaw-aligned frame for that body."""
+ body_id = _single_body_id(asset_cfg, "body_ang_vel_yaw_frame")
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_ang_vel_w = asset.data.body_ang_vel_w[:, body_id, :]
+ return quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_ang_vel_w)
+
+
+def body_projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor:
+ """Gravity projection in a configured body's local frame."""
+ body_id = _single_body_id(asset_cfg, "body_projected_gravity")
+ asset = env.scene[asset_cfg.name]
+ return quat_apply_inverse(asset.data.body_quat_w[:, body_id, :], asset.data.GRAVITY_VEC_W)
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..d272621 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -10,6 +10,7 @@ from isaaclab.utils.math import (
euler_xyz_from_quat,
quat_apply_inverse,
quat_from_euler_xyz,
+ quat_mul,
quat_rotate_inverse,
wrap_to_pi,
yaw_quat,
@@ -18,6 +19,46 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
+def _heading_yaw_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ return wrap_to_pi(yaw - heading_yaw_offset)
+
+
+def _command_is_moving(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ commands = env.command_manager.get_command(command_name)
+ return (torch.norm(commands[:, :2], dim=1) > linear_threshold) | (
+ torch.abs(commands[:, 2]) > angular_threshold
+ )
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +131,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +141,61 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_on_contact(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ sensor_cfg: SceneEntityCfg,
+ min_air_time: float = 0.05,
+ max_air_time: float = 0.25,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward completed swing times without penalizing short exploratory steps."""
+ if max_air_time <= min_air_time:
+ raise ValueError("max_air_time must be greater than min_air_time.")
+
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ completed_swing = torch.clamp(last_air_time - min_air_time, min=0.0, max=max_air_time - min_air_time)
+ reward = torch.sum(completed_swing * first_contact, dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ threshold: float,
+ sensor_cfg: SceneEntityCfg,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -132,6 +228,79 @@ def track_ang_vel_z_world_exp(
rew_abs = torch.exp(-ang_vel_error_abs * sigma)
return torch.where(stand_command, rew_abs, rew_square)
+
+def track_lin_vel_xy_yaw_frame_exp_body(
+ env,
+ sigma: float,
+ command_name: str,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Track planar velocity of a configured body in its yaw-aligned frame."""
+ body_id = _single_body_id(asset_cfg, "track_lin_vel_xy_yaw_frame_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_lin_vel_w = asset.data.body_lin_vel_w[:, body_id, :]
+ vel_yaw = quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_lin_vel_w)
+ lin_vel_error_square = torch.sum(torch.square(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ lin_vel_error_abs = torch.sum(torch.abs(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ rew_square = torch.exp(-lin_vel_error_square * sigma)
+ rew_abs = torch.exp(-lin_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_body(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track world-frame yaw angular velocity of a configured body."""
+ body_id = _single_body_id(asset_cfg, "track_ang_vel_z_world_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ ang_vel_error_square = torch.square(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ ang_vel_error_abs = torch.abs(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_bodies(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track yaw rate across all configured bodies so internal waist motion cannot satisfy the command alone."""
+ body_ids = asset_cfg.body_ids
+ if body_ids is None or isinstance(body_ids, slice):
+ raise ValueError("track_ang_vel_z_world_exp_bodies requires one or more explicitly resolved body ids.")
+
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_yaw_rates = asset.data.body_ang_vel_w[:, body_ids, 2]
+ command_yaw_rate = commands[:, 2].unsqueeze(1)
+ ang_vel_error_square = torch.mean(torch.square(command_yaw_rate - body_yaw_rates), dim=1)
+ ang_vel_error_abs = torch.mean(torch.abs(command_yaw_rate - body_yaw_rates), dim=1)
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
def feet_stumble(
env, sensor_cfg: SceneEntityCfg, tangential_threshold: float = 2.0, normal_threshold: float = 1.0
) -> torch.Tensor:
@@ -141,7 +310,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +334,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +364,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +378,66 @@ def feet_contact_fixed(
return reward
+def biped_contact_mode_reward(
+ env: ManagerBasedRLEnv,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward double support while standing and exactly one supporting foot while moving."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ contact_count = torch.sum(contacts.int(), dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return torch.where(moving, contact_count == 1, contact_count == 2).float()
+
+
+def swing_foot_clearance_reward(
+ env: ManagerBasedRLEnv,
+ asset_cfg: SceneEntityCfg,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ target_height: float,
+ std: float,
+ sole_offset: float,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward a target sole clearance only when the other foot provides support."""
+ if std <= 0.0:
+ raise ValueError("std must be positive.")
+
+ asset = env.scene[asset_cfg.name]
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ single_stance = torch.sum(contacts.int(), dim=1) == 1
+ swing_feet = ~contacts
+
+ terrain_height = env.scene.env_origins[:, 2].unsqueeze(1)
+ sole_height = asset.data.body_pos_w[:, asset_cfg.body_ids, 2] - terrain_height - sole_offset
+ clearance_reward = torch.exp(-torch.square(sole_height - target_height) / (std * std))
+ clearance_reward = torch.sum(clearance_reward * swing_feet, dim=1)
+
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return clearance_reward * single_stance * moving
+
+
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -253,6 +484,43 @@ def feet_position(env,
return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+def feet_position_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ desired_foot_positions: tuple[tuple[float, float, float], ...],
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 3.0,
+) -> torch.Tensor:
+ """Reward standing foot positions relative to a configured reference body."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_position_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = asset.data.body_pos_w[:, asset_cfg.body_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_pos_w.shape
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat_per_foot = heading_quat.unsqueeze(1).expand(-1, num_feet, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat_per_foot, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, num_feet, 3)
+
+ desired = torch.tensor(desired_foot_positions, dtype=feet_pos_heading.dtype, device=feet_pos_heading.device)
+ if desired.shape != (num_feet, 3):
+ raise ValueError(f"desired_foot_positions must have shape ({num_feet}, 3), got {tuple(desired.shape)}.")
+ position_error = torch.sum(torch.abs(feet_pos_heading - desired.unsqueeze(0)), dim=(1, 2))
+ reward_stand = torch.exp(-position_error * scale)
+ return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+
+
def feet_regulation(
env,
asset_cfg: SceneEntityCfg,
@@ -293,7 +561,7 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
@@ -350,6 +618,20 @@ def base_height_tracking(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"
height_error = torch.abs(asset.data.root_pos_w[:, 2] - target_height)
return torch.exp(-height_error * 30.0)
+
+def body_height_tracking(
+ env,
+ asset_cfg: SceneEntityCfg,
+ target_height: float,
+ scale: float = 30.0,
+) -> torch.Tensor:
+ """Reward keeping a configured body height near a target height."""
+ body_id = _single_body_id(asset_cfg, "body_height_tracking")
+ asset = env.scene[asset_cfg.name]
+ height_error = torch.abs(asset.data.body_pos_w[:, body_id, 2] - target_height)
+ return torch.exp(-height_error * scale)
+
+
def energy_cost(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize energy consumption approximated by the sum of squared joint torques."""
asset = env.scene[asset_cfg.name]
@@ -385,6 +667,50 @@ def feet_orientation(env, asset_cfg: SceneEntityCfg, command_name: str, stand_th
return torch.exp(-rew * 2.0)
+def feet_orientation_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ foot_frame_offsets_rpy: tuple[tuple[float, float, float], ...] | None = None,
+ scale: float = 2.0,
+) -> torch.Tensor:
+ """Reward physical sole orientation relative to a configured reference body's heading."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_orientation_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ yaw_command = torch.abs(commands[:, 2]) > stand_threshold
+
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ feet_quat = asset.data.body_quat_w[:, asset_cfg.body_ids, :]
+ reference_quat = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_quat.shape
+ feet_flat = feet_quat.reshape(-1, 4)
+ if foot_frame_offsets_rpy is not None:
+ offsets = torch.tensor(foot_frame_offsets_rpy, dtype=feet_quat.dtype, device=feet_quat.device)
+ if offsets.shape != (num_feet, 3):
+ raise ValueError(f"foot_frame_offsets_rpy must have shape ({num_feet}, 3), got {tuple(offsets.shape)}.")
+ offset_quat = quat_from_euler_xyz(offsets[:, 0], offsets[:, 1], offsets[:, 2])
+ offset_quat = offset_quat.unsqueeze(0).expand(num_envs, -1, -1).reshape(-1, 4)
+ feet_flat = quat_mul(feet_flat, offset_quat)
+
+ roll, pitch, yaw = euler_xyz_from_quat(feet_flat)
+ roll = roll.reshape(num_envs, num_feet)
+ pitch = pitch.reshape(num_envs, num_feet)
+ yaw = yaw.reshape(num_envs, num_feet)
+
+ reference_yaw = _heading_yaw_with_offset(reference_quat, heading_yaw_offset)
+ feet_roll_pitch_error = torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1)
+ feet_yaw_error = torch.abs(wrap_to_pi(yaw - reference_yaw.unsqueeze(1)))
+
+ rew = torch.sum(feet_roll_pitch_error + feet_yaw_error, dim=1)
+ rew[yaw_command] = torch.sum(feet_roll_pitch_error[yaw_command], dim=1)
+ return torch.exp(-rew * scale)
+
+
def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Reward keeping the base roll/pitch near zero."""
asset = env.scene[asset_cfg.name]
@@ -392,6 +718,50 @@ def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -
base_euler = torch.stack((roll, pitch, yaw), dim=-1)
return torch.exp(-torch.sum(torch.abs(base_euler[:, :2]), dim=-1) * 10.0)
+
+def body_orientation(env, asset_cfg: SceneEntityCfg, scale: float = 10.0) -> torch.Tensor:
+ """Reward keeping a configured body's roll/pitch near zero."""
+ body_id = _single_body_id(asset_cfg, "body_orientation")
+ asset = env.scene[asset_cfg.name]
+ roll, pitch, yaw = euler_xyz_from_quat(asset.data.body_quat_w[:, body_id, :])
+ return torch.exp(-torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1) * scale)
+
+
+def body_yaw_alignment(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+ reference_heading_yaw_offset: float = 0.0,
+ scale: float = 4.0,
+) -> torch.Tensor:
+ """Reward keeping one body's heading aligned with another body's heading."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_alignment")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_alignment")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ body_yaw = _heading_yaw_with_offset(asset.data.body_quat_w[:, body_id, :], heading_yaw_offset)
+ reference_yaw = _heading_yaw_with_offset(
+ reference_asset.data.body_quat_w[:, reference_body_id, :], reference_heading_yaw_offset
+ )
+ return torch.exp(-torch.abs(wrap_to_pi(body_yaw - reference_yaw)) * scale)
+
+
+def body_yaw_rate_difference_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+) -> torch.Tensor:
+ """Penalize relative world-frame yaw rate between two configured bodies."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_rate_difference_l2")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_rate_difference_l2")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ yaw_rate = asset.data.body_ang_vel_w[:, body_id, 2]
+ reference_yaw_rate = reference_asset.data.body_ang_vel_w[:, reference_body_id, 2]
+ return torch.square(yaw_rate - reference_yaw_rate)
+
+
def reward_waist_pos(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
@@ -412,7 +782,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 1200
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_gait_reward_v2
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: false
load_run: .*
load_checkpoint: model_.*.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,654 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/observations.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..553a153 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -3,3 +3,4 @@ from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/observations.py b/source/engineai_lab/tasks/velocity/mdp/observations.py
index 0881dba..9dc20ff 100644
--- a/source/engineai_lab/tasks/velocity/mdp/observations.py
+++ b/source/engineai_lab/tasks/velocity/mdp/observations.py
@@ -3,12 +3,30 @@ from __future__ import annotations
import torch
from typing import TYPE_CHECKING
-from isaaclab.utils.math import quat_apply_inverse
+from isaaclab.managers import SceneEntityCfg
+from isaaclab.utils.math import euler_xyz_from_quat, quat_apply_inverse, quat_from_euler_xyz
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
"""Base linear velocity expressed in the base frame."""
asset = env.scene["robot"]
@@ -18,3 +36,22 @@ def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
# fallback: rotate world velocity into base frame
return quat_apply_inverse(asset.data.root_quat_w, asset.data.root_lin_vel_w)
+
+def body_ang_vel_yaw_frame(
+ env: ManagerBasedEnv,
+ asset_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Body angular velocity expressed in a yaw-aligned frame for that body."""
+ body_id = _single_body_id(asset_cfg, "body_ang_vel_yaw_frame")
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_ang_vel_w = asset.data.body_ang_vel_w[:, body_id, :]
+ return quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_ang_vel_w)
+
+
+def body_projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor:
+ """Gravity projection in a configured body's local frame."""
+ body_id = _single_body_id(asset_cfg, "body_projected_gravity")
+ asset = env.scene[asset_cfg.name]
+ return quat_apply_inverse(asset.data.body_quat_w[:, body_id, :], asset.data.GRAVITY_VEC_W)
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..cd7cd8f 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -10,6 +10,7 @@ from isaaclab.utils.math import (
euler_xyz_from_quat,
quat_apply_inverse,
quat_from_euler_xyz,
+ quat_mul,
quat_rotate_inverse,
wrap_to_pi,
yaw_quat,
@@ -18,6 +19,46 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
+def _heading_yaw_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ return wrap_to_pi(yaw - heading_yaw_offset)
+
+
+def _command_is_moving(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ commands = env.command_manager.get_command(command_name)
+ return (torch.norm(commands[:, :2], dim=1) > linear_threshold) | (
+ torch.abs(commands[:, 2]) > angular_threshold
+ )
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +131,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +141,61 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_on_contact(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ sensor_cfg: SceneEntityCfg,
+ min_air_time: float = 0.05,
+ max_air_time: float = 0.25,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward completed swing times without penalizing short exploratory steps."""
+ if max_air_time <= min_air_time:
+ raise ValueError("max_air_time must be greater than min_air_time.")
+
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ completed_swing = torch.clamp(last_air_time - min_air_time, min=0.0, max=max_air_time - min_air_time)
+ reward = torch.sum(completed_swing * first_contact, dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ threshold: float,
+ sensor_cfg: SceneEntityCfg,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -132,6 +228,79 @@ def track_ang_vel_z_world_exp(
rew_abs = torch.exp(-ang_vel_error_abs * sigma)
return torch.where(stand_command, rew_abs, rew_square)
+
+def track_lin_vel_xy_yaw_frame_exp_body(
+ env,
+ sigma: float,
+ command_name: str,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Track planar velocity of a configured body in its yaw-aligned frame."""
+ body_id = _single_body_id(asset_cfg, "track_lin_vel_xy_yaw_frame_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_lin_vel_w = asset.data.body_lin_vel_w[:, body_id, :]
+ vel_yaw = quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_lin_vel_w)
+ lin_vel_error_square = torch.sum(torch.square(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ lin_vel_error_abs = torch.sum(torch.abs(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ rew_square = torch.exp(-lin_vel_error_square * sigma)
+ rew_abs = torch.exp(-lin_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_body(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track world-frame yaw angular velocity of a configured body."""
+ body_id = _single_body_id(asset_cfg, "track_ang_vel_z_world_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ ang_vel_error_square = torch.square(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ ang_vel_error_abs = torch.abs(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_bodies(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track yaw rate across all configured bodies so internal waist motion cannot satisfy the command alone."""
+ body_ids = asset_cfg.body_ids
+ if body_ids is None or isinstance(body_ids, slice):
+ raise ValueError("track_ang_vel_z_world_exp_bodies requires one or more explicitly resolved body ids.")
+
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_yaw_rates = asset.data.body_ang_vel_w[:, body_ids, 2]
+ command_yaw_rate = commands[:, 2].unsqueeze(1)
+ ang_vel_error_square = torch.mean(torch.square(command_yaw_rate - body_yaw_rates), dim=1)
+ ang_vel_error_abs = torch.mean(torch.abs(command_yaw_rate - body_yaw_rates), dim=1)
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
def feet_stumble(
env, sensor_cfg: SceneEntityCfg, tangential_threshold: float = 2.0, normal_threshold: float = 1.0
) -> torch.Tensor:
@@ -141,7 +310,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +334,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +364,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +378,68 @@ def feet_contact_fixed(
return reward
+def biped_contact_mode_reward(
+ env: ManagerBasedRLEnv,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward double support while standing and exactly one supporting foot while moving."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ contact_count = torch.sum(contacts.int(), dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return torch.where(moving, contact_count == 1, contact_count == 2).float()
+
+
+def swing_foot_clearance_reward(
+ env: ManagerBasedRLEnv,
+ asset_cfg: SceneEntityCfg,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ target_height: float,
+ std: float,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward swing-foot height relative to the supporting foot."""
+ if std <= 0.0:
+ raise ValueError("std must be positive.")
+
+ asset = env.scene[asset_cfg.name]
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ single_stance = torch.sum(contacts.int(), dim=1) == 1
+ swing_feet = ~contacts
+
+ foot_height = asset.data.body_pos_w[:, asset_cfg.body_ids, 2]
+ if foot_height.shape[1] != contacts.shape[1]:
+ raise ValueError("asset_cfg and sensor_cfg must resolve the same number of feet.")
+ stance_height = torch.sum(foot_height * contacts, dim=1, keepdim=True)
+ swing_clearance = foot_height - stance_height
+ clearance_reward = torch.exp(-torch.square(swing_clearance - target_height) / (std * std))
+ clearance_reward = torch.sum(clearance_reward * swing_feet, dim=1)
+
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return clearance_reward * single_stance * moving
+
+
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -253,6 +486,43 @@ def feet_position(env,
return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+def feet_position_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ desired_foot_positions: tuple[tuple[float, float, float], ...],
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 3.0,
+) -> torch.Tensor:
+ """Reward standing foot positions relative to a configured reference body."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_position_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = asset.data.body_pos_w[:, asset_cfg.body_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_pos_w.shape
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat_per_foot = heading_quat.unsqueeze(1).expand(-1, num_feet, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat_per_foot, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, num_feet, 3)
+
+ desired = torch.tensor(desired_foot_positions, dtype=feet_pos_heading.dtype, device=feet_pos_heading.device)
+ if desired.shape != (num_feet, 3):
+ raise ValueError(f"desired_foot_positions must have shape ({num_feet}, 3), got {tuple(desired.shape)}.")
+ position_error = torch.sum(torch.abs(feet_pos_heading - desired.unsqueeze(0)), dim=(1, 2))
+ reward_stand = torch.exp(-position_error * scale)
+ return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+
+
def feet_regulation(
env,
asset_cfg: SceneEntityCfg,
@@ -293,7 +563,7 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
@@ -350,6 +620,20 @@ def base_height_tracking(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"
height_error = torch.abs(asset.data.root_pos_w[:, 2] - target_height)
return torch.exp(-height_error * 30.0)
+
+def body_height_tracking(
+ env,
+ asset_cfg: SceneEntityCfg,
+ target_height: float,
+ scale: float = 30.0,
+) -> torch.Tensor:
+ """Reward keeping a configured body height near a target height."""
+ body_id = _single_body_id(asset_cfg, "body_height_tracking")
+ asset = env.scene[asset_cfg.name]
+ height_error = torch.abs(asset.data.body_pos_w[:, body_id, 2] - target_height)
+ return torch.exp(-height_error * scale)
+
+
def energy_cost(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize energy consumption approximated by the sum of squared joint torques."""
asset = env.scene[asset_cfg.name]
@@ -385,6 +669,50 @@ def feet_orientation(env, asset_cfg: SceneEntityCfg, command_name: str, stand_th
return torch.exp(-rew * 2.0)
+def feet_orientation_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ foot_frame_offsets_rpy: tuple[tuple[float, float, float], ...] | None = None,
+ scale: float = 2.0,
+) -> torch.Tensor:
+ """Reward physical sole orientation relative to a configured reference body's heading."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_orientation_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ yaw_command = torch.abs(commands[:, 2]) > stand_threshold
+
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ feet_quat = asset.data.body_quat_w[:, asset_cfg.body_ids, :]
+ reference_quat = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_quat.shape
+ feet_flat = feet_quat.reshape(-1, 4)
+ if foot_frame_offsets_rpy is not None:
+ offsets = torch.tensor(foot_frame_offsets_rpy, dtype=feet_quat.dtype, device=feet_quat.device)
+ if offsets.shape != (num_feet, 3):
+ raise ValueError(f"foot_frame_offsets_rpy must have shape ({num_feet}, 3), got {tuple(offsets.shape)}.")
+ offset_quat = quat_from_euler_xyz(offsets[:, 0], offsets[:, 1], offsets[:, 2])
+ offset_quat = offset_quat.unsqueeze(0).expand(num_envs, -1, -1).reshape(-1, 4)
+ feet_flat = quat_mul(feet_flat, offset_quat)
+
+ roll, pitch, yaw = euler_xyz_from_quat(feet_flat)
+ roll = roll.reshape(num_envs, num_feet)
+ pitch = pitch.reshape(num_envs, num_feet)
+ yaw = yaw.reshape(num_envs, num_feet)
+
+ reference_yaw = _heading_yaw_with_offset(reference_quat, heading_yaw_offset)
+ feet_roll_pitch_error = torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1)
+ feet_yaw_error = torch.abs(wrap_to_pi(yaw - reference_yaw.unsqueeze(1)))
+
+ rew = torch.sum(feet_roll_pitch_error + feet_yaw_error, dim=1)
+ rew[yaw_command] = torch.sum(feet_roll_pitch_error[yaw_command], dim=1)
+ return torch.exp(-rew * scale)
+
+
def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Reward keeping the base roll/pitch near zero."""
asset = env.scene[asset_cfg.name]
@@ -392,6 +720,50 @@ def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -
base_euler = torch.stack((roll, pitch, yaw), dim=-1)
return torch.exp(-torch.sum(torch.abs(base_euler[:, :2]), dim=-1) * 10.0)
+
+def body_orientation(env, asset_cfg: SceneEntityCfg, scale: float = 10.0) -> torch.Tensor:
+ """Reward keeping a configured body's roll/pitch near zero."""
+ body_id = _single_body_id(asset_cfg, "body_orientation")
+ asset = env.scene[asset_cfg.name]
+ roll, pitch, yaw = euler_xyz_from_quat(asset.data.body_quat_w[:, body_id, :])
+ return torch.exp(-torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1) * scale)
+
+
+def body_yaw_alignment(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+ reference_heading_yaw_offset: float = 0.0,
+ scale: float = 4.0,
+) -> torch.Tensor:
+ """Reward keeping one body's heading aligned with another body's heading."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_alignment")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_alignment")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ body_yaw = _heading_yaw_with_offset(asset.data.body_quat_w[:, body_id, :], heading_yaw_offset)
+ reference_yaw = _heading_yaw_with_offset(
+ reference_asset.data.body_quat_w[:, reference_body_id, :], reference_heading_yaw_offset
+ )
+ return torch.exp(-torch.abs(wrap_to_pi(body_yaw - reference_yaw)) * scale)
+
+
+def body_yaw_rate_difference_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+) -> torch.Tensor:
+ """Penalize relative world-frame yaw rate between two configured bodies."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_rate_difference_l2")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_rate_difference_l2")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ yaw_rate = asset.data.body_ang_vel_w[:, body_id, 2]
+ reference_yaw_rate = reference_asset.data.body_ang_vel_w[:, reference_body_id, 2]
+ return torch.square(yaw_rate - reference_yaw_rate)
+
+
def reward_waist_pos(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
@@ -412,7 +784,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 800
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_gait_clearance_stage2
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: true
load_run: 2026-07-10_14-02-12_gen2_gait_reward_v2
load_checkpoint: model_1199.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,956 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/play.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/observations.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/commands.py
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/play.py b/scripts/play.py
index f19b9cb..1187ff6 100644
--- a/scripts/play.py
+++ b/scripts/play.py
@@ -1,4 +1,4 @@
-"""Script to play a checkpoint if an RL agent from RSL-RL."""
+"""Script to play a checkpoint from an RSL-RL agent."""
"""Launch Isaac Sim Simulator first."""
@@ -17,18 +17,49 @@ if str(REPO_ROOT) not in sys.path:
sys.path.append(str(REPO_ROOT))
# add argparse arguments
-parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.")
+parser = argparse.ArgumentParser(description="Play an RSL-RL policy checkpoint.")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
+parser.add_argument("--max_steps", type=int, default=None, help="Stop replay after this many policy steps.")
+
+command_group = parser.add_argument_group("velocity command", description="Velocity command source during replay.")
+command_group.add_argument(
+ "--command_source",
+ type=str,
+ choices=("random", "fixed", "keyboard"),
+ default="random",
+ help="Use environment-generated, fixed, or keyboard velocity commands.",
+)
+command_group.add_argument("--vx", type=float, default=0.3, help="Fixed forward velocity in m/s.")
+command_group.add_argument("--vy", type=float, default=0.0, help="Fixed lateral velocity in m/s.")
+command_group.add_argument("--wz", type=float, default=0.0, help="Fixed yaw velocity in rad/s.")
+command_group.add_argument(
+ "--command_mode",
+ type=str,
+ choices=("step", "ramp"),
+ default="ramp",
+ help="Apply external commands immediately or through acceleration limits.",
+)
+command_group.add_argument("--linear_accel", type=float, default=0.8, help="Planar acceleration limit in m/s^2.")
+command_group.add_argument("--yaw_accel", type=float, default=1.5, help="Yaw acceleration limit in rad/s^2.")
+command_group.add_argument("--keyboard_vx", type=float, default=0.4, help="Keyboard forward velocity in m/s.")
+command_group.add_argument("--keyboard_vy", type=float, default=0.2, help="Keyboard lateral velocity in m/s.")
+command_group.add_argument("--keyboard_wz", type=float, default=0.5, help="Keyboard yaw velocity in rad/s.")
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
args_cli, hydra_args = parser.parse_known_args()
+if args_cli.linear_accel <= 0.0:
+ parser.error("--linear_accel must be positive.")
+if args_cli.yaw_accel <= 0.0:
+ parser.error("--yaw_accel must be positive.")
+if args_cli.max_steps is not None and args_cli.max_steps <= 0:
+ parser.error("--max_steps must be positive.")
# always enable cameras to record video
# clear out sys.argv for Hydra
sys.argv = [sys.argv[0]] + hydra_args
@@ -39,9 +70,9 @@ simulation_app = app_launcher.app
"""Rest everything follows."""
-import gymnasium as gym
import os
-import pathlib
+
+import gymnasium as gym
import torch
from rsl_rl.runners import OnPolicyRunner
@@ -53,7 +84,6 @@ from isaaclab.envs import (
ManagerBasedRLEnvCfg,
multi_agent_to_single_agent,
)
-from isaaclab.utils.dict import print_dict
from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlVecEnvWrapper
from isaaclab_tasks.utils import get_checkpoint_path
from isaaclab_tasks.utils.hydra import hydra_task_config
@@ -62,6 +92,67 @@ from isaaclab_tasks.utils.hydra import hydra_task_config
import engineai_lab.tasks # noqa: F401
+class VelocityCommandController:
+ """Apply fixed or keyboard commands directly to a velocity command term."""
+
+ def __init__(self, env: RslRlVecEnvWrapper, args: argparse.Namespace):
+ self.env = env
+ self.args = args
+ self.command_term = env.unwrapped.command_manager.get_term("base_velocity")
+ if not hasattr(self.command_term, "vel_command_b"):
+ raise TypeError("The base_velocity command term does not expose a velocity command buffer.")
+
+ self.current_command = torch.zeros(3, device=env.unwrapped.device)
+ self.fixed_command = torch.tensor([args.vx, args.vy, args.wz], device=env.unwrapped.device)
+ self.keyboard = None
+
+ if args.command_source == "keyboard":
+ from isaaclab.devices import Se2Keyboard, Se2KeyboardCfg
+
+ keyboard_cfg = Se2KeyboardCfg(
+ sim_device=str(env.unwrapped.device),
+ v_x_sensitivity=args.keyboard_vx,
+ v_y_sensitivity=args.keyboard_vy,
+ omega_z_sensitivity=args.keyboard_wz,
+ )
+ self.keyboard = Se2Keyboard(keyboard_cfg)
+ print(self.keyboard)
+ else:
+ print(
+ "[INFO] Fixed velocity command: "
+ f"vx={args.vx:.3f} m/s, vy={args.vy:.3f} m/s, wz={args.wz:.3f} rad/s"
+ )
+ print(f"[INFO] External command mode: {args.command_mode}")
+
+ def update(self):
+ target = self.keyboard.advance() if self.keyboard is not None else self.fixed_command
+ if self.args.command_mode == "step":
+ self.current_command.copy_(target)
+ else:
+ self._apply_ramp(target)
+
+ self.command_term.vel_command_b[:] = self.current_command.unsqueeze(0)
+ self.command_term.time_left.fill_(float("inf"))
+ self.command_term.is_standing_env.fill_(False)
+ if hasattr(self.command_term, "is_heading_env"):
+ self.command_term.is_heading_env.fill_(False)
+
+ def _apply_ramp(self, target: torch.Tensor):
+ dt = self.env.unwrapped.step_dt
+ linear_delta = target[:2] - self.current_command[:2]
+ linear_delta_norm = torch.linalg.norm(linear_delta)
+ max_linear_delta = self.args.linear_accel * dt
+ linear_scale = torch.clamp(max_linear_delta / torch.clamp(linear_delta_norm, min=1.0e-6), max=1.0)
+ linear_delta = linear_delta * linear_scale
+ self.current_command[:2] += linear_delta
+
+ yaw_delta = torch.clamp(
+ target[2] - self.current_command[2],
+ min=-self.args.yaw_accel * dt,
+ max=self.args.yaw_accel * dt,
+ )
+ self.current_command[2] += yaw_delta
+
@hydra_task_config(args_cli.task, "rsl_rl_cfg_entry_point")
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlOnPolicyRunnerCfg):
@@ -83,7 +174,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# create isaac environment
env = gym.make(args_cli.task, cfg=env_cfg, render_mode=None)
-
# convert to single-agent instance if required by the RL algorithm
if isinstance(env.unwrapped, DirectMARLEnv):
env = multi_agent_to_single_agent(env)
@@ -91,6 +181,10 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# wrap around environment for rsl-rl
env = RslRlVecEnvWrapper(env)
+ command_controller = None
+ if args_cli.command_source != "random":
+ command_controller = VelocityCommandController(env, args_cli)
+
# load previously trained model
ppo_runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
ppo_runner.load(resume_path)
@@ -103,18 +197,24 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
file_basename = os.path.basename(resume_path).split(".")[0]
ppo_runner.export_policy_to_onnx(path=export_model_dir, filename=file_basename+".onnx")
-
# reset environment
obs = env.get_observations()
- timestep = 0
+ step_count = 0
# simulate environment
- while simulation_app.is_running():
+ while simulation_app.is_running() and (args_cli.max_steps is None or step_count < args_cli.max_steps):
# run everything in inference mode
with torch.inference_mode():
+ if command_controller is not None:
+ command_controller.update()
+ obs = env.get_observations()
# agent stepping
actions = policy(obs)
# env stepping
obs, _, _, _ = env.step(actions)
+ step_count += 1
+
+ if args_cli.max_steps is not None:
+ print(f"[INFO] Replay completed after {step_count} policy steps.")
# close the simulator
env.close()
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..c885999 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -1,5 +1,7 @@
from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
+from .commands import * # noqa: F401, F403
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/observations.py b/source/engineai_lab/tasks/velocity/mdp/observations.py
index 0881dba..9dc20ff 100644
--- a/source/engineai_lab/tasks/velocity/mdp/observations.py
+++ b/source/engineai_lab/tasks/velocity/mdp/observations.py
@@ -3,12 +3,30 @@ from __future__ import annotations
import torch
from typing import TYPE_CHECKING
-from isaaclab.utils.math import quat_apply_inverse
+from isaaclab.managers import SceneEntityCfg
+from isaaclab.utils.math import euler_xyz_from_quat, quat_apply_inverse, quat_from_euler_xyz
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
"""Base linear velocity expressed in the base frame."""
asset = env.scene["robot"]
@@ -18,3 +36,22 @@ def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
# fallback: rotate world velocity into base frame
return quat_apply_inverse(asset.data.root_quat_w, asset.data.root_lin_vel_w)
+
+def body_ang_vel_yaw_frame(
+ env: ManagerBasedEnv,
+ asset_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Body angular velocity expressed in a yaw-aligned frame for that body."""
+ body_id = _single_body_id(asset_cfg, "body_ang_vel_yaw_frame")
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_ang_vel_w = asset.data.body_ang_vel_w[:, body_id, :]
+ return quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_ang_vel_w)
+
+
+def body_projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor:
+ """Gravity projection in a configured body's local frame."""
+ body_id = _single_body_id(asset_cfg, "body_projected_gravity")
+ asset = env.scene[asset_cfg.name]
+ return quat_apply_inverse(asset.data.body_quat_w[:, body_id, :], asset.data.GRAVITY_VEC_W)
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..ec938fe 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -10,6 +10,7 @@ from isaaclab.utils.math import (
euler_xyz_from_quat,
quat_apply_inverse,
quat_from_euler_xyz,
+ quat_mul,
quat_rotate_inverse,
wrap_to_pi,
yaw_quat,
@@ -18,6 +19,46 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
+def _heading_yaw_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ return wrap_to_pi(yaw - heading_yaw_offset)
+
+
+def _command_is_moving(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ commands = env.command_manager.get_command(command_name)
+ return (torch.norm(commands[:, :2], dim=1) > linear_threshold) | (
+ torch.abs(commands[:, 2]) > angular_threshold
+ )
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +131,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +141,61 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_on_contact(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ sensor_cfg: SceneEntityCfg,
+ min_air_time: float = 0.05,
+ max_air_time: float = 0.25,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward completed swing times without penalizing short exploratory steps."""
+ if max_air_time <= min_air_time:
+ raise ValueError("max_air_time must be greater than min_air_time.")
+
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ completed_swing = torch.clamp(last_air_time - min_air_time, min=0.0, max=max_air_time - min_air_time)
+ reward = torch.sum(completed_swing * first_contact, dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ threshold: float,
+ sensor_cfg: SceneEntityCfg,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -132,6 +228,79 @@ def track_ang_vel_z_world_exp(
rew_abs = torch.exp(-ang_vel_error_abs * sigma)
return torch.where(stand_command, rew_abs, rew_square)
+
+def track_lin_vel_xy_yaw_frame_exp_body(
+ env,
+ sigma: float,
+ command_name: str,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Track planar velocity of a configured body in its yaw-aligned frame."""
+ body_id = _single_body_id(asset_cfg, "track_lin_vel_xy_yaw_frame_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_lin_vel_w = asset.data.body_lin_vel_w[:, body_id, :]
+ vel_yaw = quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_lin_vel_w)
+ lin_vel_error_square = torch.sum(torch.square(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ lin_vel_error_abs = torch.sum(torch.abs(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ rew_square = torch.exp(-lin_vel_error_square * sigma)
+ rew_abs = torch.exp(-lin_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_body(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track world-frame yaw angular velocity of a configured body."""
+ body_id = _single_body_id(asset_cfg, "track_ang_vel_z_world_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ ang_vel_error_square = torch.square(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ ang_vel_error_abs = torch.abs(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_bodies(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track yaw rate across all configured bodies so internal waist motion cannot satisfy the command alone."""
+ body_ids = asset_cfg.body_ids
+ if body_ids is None or isinstance(body_ids, slice):
+ raise ValueError("track_ang_vel_z_world_exp_bodies requires one or more explicitly resolved body ids.")
+
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_yaw_rates = asset.data.body_ang_vel_w[:, body_ids, 2]
+ command_yaw_rate = commands[:, 2].unsqueeze(1)
+ ang_vel_error_square = torch.mean(torch.square(command_yaw_rate - body_yaw_rates), dim=1)
+ ang_vel_error_abs = torch.mean(torch.abs(command_yaw_rate - body_yaw_rates), dim=1)
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
def feet_stumble(
env, sensor_cfg: SceneEntityCfg, tangential_threshold: float = 2.0, normal_threshold: float = 1.0
) -> torch.Tensor:
@@ -141,7 +310,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +334,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +364,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +378,68 @@ def feet_contact_fixed(
return reward
+def biped_contact_mode_reward(
+ env: ManagerBasedRLEnv,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward double support while standing and exactly one supporting foot while moving."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ contact_count = torch.sum(contacts.int(), dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return torch.where(moving, contact_count == 1, contact_count == 2).float()
+
+
+def swing_foot_clearance_reward(
+ env: ManagerBasedRLEnv,
+ asset_cfg: SceneEntityCfg,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ target_height: float,
+ std: float,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward swing-foot height relative to the supporting foot."""
+ if std <= 0.0:
+ raise ValueError("std must be positive.")
+
+ asset = env.scene[asset_cfg.name]
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ single_stance = torch.sum(contacts.int(), dim=1) == 1
+ swing_feet = ~contacts
+
+ foot_height = asset.data.body_pos_w[:, asset_cfg.body_ids, 2]
+ if foot_height.shape[1] != contacts.shape[1]:
+ raise ValueError("asset_cfg and sensor_cfg must resolve the same number of feet.")
+ stance_height = torch.sum(foot_height * contacts, dim=1, keepdim=True)
+ swing_clearance = foot_height - stance_height
+ clearance_reward = torch.exp(-torch.square(swing_clearance - target_height) / (std * std))
+ clearance_reward = torch.sum(clearance_reward * swing_feet, dim=1)
+
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return clearance_reward * single_stance * moving
+
+
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -253,6 +486,43 @@ def feet_position(env,
return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+def feet_position_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ desired_foot_positions: tuple[tuple[float, float, float], ...],
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 3.0,
+) -> torch.Tensor:
+ """Reward standing foot positions relative to a configured reference body."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_position_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = asset.data.body_pos_w[:, asset_cfg.body_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_pos_w.shape
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat_per_foot = heading_quat.unsqueeze(1).expand(-1, num_feet, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat_per_foot, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, num_feet, 3)
+
+ desired = torch.tensor(desired_foot_positions, dtype=feet_pos_heading.dtype, device=feet_pos_heading.device)
+ if desired.shape != (num_feet, 3):
+ raise ValueError(f"desired_foot_positions must have shape ({num_feet}, 3), got {tuple(desired.shape)}.")
+ position_error = torch.sum(torch.abs(feet_pos_heading - desired.unsqueeze(0)), dim=(1, 2))
+ reward_stand = torch.exp(-position_error * scale)
+ return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+
+
def feet_regulation(
env,
asset_cfg: SceneEntityCfg,
@@ -293,10 +563,10 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
- foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
+ foot_vel_z = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, 2]
landing_speed = torch.clamp(-foot_vel_z - velocity_threshold, min=0.0)
penalty = torch.sum(torch.pow(landing_speed, power) * first_contact, dim=1)
return penalty
@@ -350,6 +620,117 @@ def base_height_tracking(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"
height_error = torch.abs(asset.data.root_pos_w[:, 2] - target_height)
return torch.exp(-height_error * 30.0)
+
+def body_height_tracking(
+ env,
+ asset_cfg: SceneEntityCfg,
+ target_height: float,
+ scale: float = 30.0,
+) -> torch.Tensor:
+ """Reward keeping a configured body height near a target height."""
+ body_id = _single_body_id(asset_cfg, "body_height_tracking")
+ asset = env.scene[asset_cfg.name]
+ height_error = torch.abs(asset.data.body_pos_w[:, body_id, 2] - target_height)
+ return torch.exp(-height_error * scale)
+
+
+def body_vertical_velocity_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ deadband: float = 0.0,
+) -> torch.Tensor:
+ """Penalize vertical body velocity outside a small natural-motion deadband."""
+ body_id = _single_body_id(asset_cfg, "body_vertical_velocity_l2")
+ asset = env.scene[asset_cfg.name]
+ vertical_speed = torch.abs(asset.data.body_lin_vel_w[:, body_id, 2])
+ return torch.square(torch.clamp(vertical_speed - deadband, min=0.0))
+
+
+def body_roll_pitch_ang_vel_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ deadband: float = 0.0,
+) -> torch.Tensor:
+ """Penalize horizontal angular speed while allowing normal gait oscillation."""
+ body_id = _single_body_id(asset_cfg, "body_roll_pitch_ang_vel_l2")
+ asset = env.scene[asset_cfg.name]
+ horizontal_ang_speed = torch.linalg.norm(asset.data.body_ang_vel_w[:, body_id, :2], dim=1)
+ return torch.square(torch.clamp(horizontal_ang_speed - deadband, min=0.0))
+
+
+def cross_body_arm_swing_reward(
+ env,
+ arm_asset_cfg: SceneEntityCfg,
+ feet_asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ heading_yaw_offset: float = 0.0,
+ min_forward_speed: float = 0.1,
+ full_swing_speed: float = 0.6,
+ phase_distance: float = 0.25,
+ shoulder_amplitude: float = 0.18,
+ elbow_flexion: float = 0.15,
+ shoulder_phase_signs: tuple[float, float] = (-1.0, -1.0),
+ elbow_flexion_signs: tuple[float, float] = (1.0, -1.0),
+ std: float = 0.2,
+) -> torch.Tensor:
+ """Track speed-scaled shoulder and elbow targets that oppose the leg phase.
+
+ Arm joints must be ordered as left/right shoulder pitch followed by left/right elbow flexion.
+ Feet must be ordered left then right.
+ """
+ if full_swing_speed <= min_forward_speed:
+ raise ValueError("full_swing_speed must be greater than min_forward_speed.")
+ if phase_distance <= 0.0 or std <= 0.0:
+ raise ValueError("phase_distance and std must be positive.")
+
+ joint_ids = arm_asset_cfg.joint_ids
+ foot_ids = feet_asset_cfg.body_ids
+ if joint_ids is None or isinstance(joint_ids, slice) or len(joint_ids) != 4:
+ raise ValueError("cross_body_arm_swing_reward requires four ordered arm joint ids.")
+ if foot_ids is None or isinstance(foot_ids, slice) or len(foot_ids) != 2:
+ raise ValueError("cross_body_arm_swing_reward requires two ordered foot body ids.")
+
+ reference_body_id = _single_body_id(reference_body_cfg, "cross_body_arm_swing_reward")
+ asset = env.scene[arm_asset_cfg.name]
+ feet_asset = env.scene[feet_asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = feet_asset.data.body_pos_w[:, foot_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+
+ num_envs = feet_pos_w.shape[0]
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat = heading_quat.unsqueeze(1).expand(-1, 2, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, 2, 3)
+ leg_phase = torch.clamp(
+ (feet_pos_heading[:, 0, 0] - feet_pos_heading[:, 1, 0]) / phase_distance,
+ min=-1.0,
+ max=1.0,
+ )
+
+ commands = env.command_manager.get_command(command_name)
+ speed_scale = torch.clamp(
+ (torch.abs(commands[:, 0]) - min_forward_speed) / (full_swing_speed - min_forward_speed),
+ min=0.0,
+ max=1.0,
+ )
+
+ joint_pos = asset.data.joint_pos[:, joint_ids]
+ target_pos = asset.data.default_joint_pos[:, joint_ids].clone()
+ shoulder_signs = joint_pos.new_tensor(shoulder_phase_signs)
+ elbow_signs = joint_pos.new_tensor(elbow_flexion_signs)
+ target_pos[:, :2] += (
+ shoulder_amplitude * speed_scale * leg_phase
+ ).unsqueeze(1) * shoulder_signs.unsqueeze(0)
+ target_pos[:, 2:] += (elbow_flexion * speed_scale).unsqueeze(1) * elbow_signs.unsqueeze(0)
+
+ mean_square_error = torch.mean(torch.square(joint_pos - target_pos), dim=1)
+ return torch.exp(-mean_square_error / (std * std))
+
+
def energy_cost(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize energy consumption approximated by the sum of squared joint torques."""
asset = env.scene[asset_cfg.name]
@@ -385,6 +766,50 @@ def feet_orientation(env, asset_cfg: SceneEntityCfg, command_name: str, stand_th
return torch.exp(-rew * 2.0)
+def feet_orientation_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ foot_frame_offsets_rpy: tuple[tuple[float, float, float], ...] | None = None,
+ scale: float = 2.0,
+) -> torch.Tensor:
+ """Reward physical sole orientation relative to a configured reference body's heading."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_orientation_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ yaw_command = torch.abs(commands[:, 2]) > stand_threshold
+
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ feet_quat = asset.data.body_quat_w[:, asset_cfg.body_ids, :]
+ reference_quat = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_quat.shape
+ feet_flat = feet_quat.reshape(-1, 4)
+ if foot_frame_offsets_rpy is not None:
+ offsets = torch.tensor(foot_frame_offsets_rpy, dtype=feet_quat.dtype, device=feet_quat.device)
+ if offsets.shape != (num_feet, 3):
+ raise ValueError(f"foot_frame_offsets_rpy must have shape ({num_feet}, 3), got {tuple(offsets.shape)}.")
+ offset_quat = quat_from_euler_xyz(offsets[:, 0], offsets[:, 1], offsets[:, 2])
+ offset_quat = offset_quat.unsqueeze(0).expand(num_envs, -1, -1).reshape(-1, 4)
+ feet_flat = quat_mul(feet_flat, offset_quat)
+
+ roll, pitch, yaw = euler_xyz_from_quat(feet_flat)
+ roll = roll.reshape(num_envs, num_feet)
+ pitch = pitch.reshape(num_envs, num_feet)
+ yaw = yaw.reshape(num_envs, num_feet)
+
+ reference_yaw = _heading_yaw_with_offset(reference_quat, heading_yaw_offset)
+ feet_roll_pitch_error = torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1)
+ feet_yaw_error = torch.abs(wrap_to_pi(yaw - reference_yaw.unsqueeze(1)))
+
+ rew = torch.sum(feet_roll_pitch_error + feet_yaw_error, dim=1)
+ rew[yaw_command] = torch.sum(feet_roll_pitch_error[yaw_command], dim=1)
+ return torch.exp(-rew * scale)
+
+
def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Reward keeping the base roll/pitch near zero."""
asset = env.scene[asset_cfg.name]
@@ -392,6 +817,50 @@ def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -
base_euler = torch.stack((roll, pitch, yaw), dim=-1)
return torch.exp(-torch.sum(torch.abs(base_euler[:, :2]), dim=-1) * 10.0)
+
+def body_orientation(env, asset_cfg: SceneEntityCfg, scale: float = 10.0) -> torch.Tensor:
+ """Reward keeping a configured body's roll/pitch near zero."""
+ body_id = _single_body_id(asset_cfg, "body_orientation")
+ asset = env.scene[asset_cfg.name]
+ roll, pitch, yaw = euler_xyz_from_quat(asset.data.body_quat_w[:, body_id, :])
+ return torch.exp(-torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1) * scale)
+
+
+def body_yaw_alignment(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+ reference_heading_yaw_offset: float = 0.0,
+ scale: float = 4.0,
+) -> torch.Tensor:
+ """Reward keeping one body's heading aligned with another body's heading."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_alignment")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_alignment")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ body_yaw = _heading_yaw_with_offset(asset.data.body_quat_w[:, body_id, :], heading_yaw_offset)
+ reference_yaw = _heading_yaw_with_offset(
+ reference_asset.data.body_quat_w[:, reference_body_id, :], reference_heading_yaw_offset
+ )
+ return torch.exp(-torch.abs(wrap_to_pi(body_yaw - reference_yaw)) * scale)
+
+
+def body_yaw_rate_difference_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+) -> torch.Tensor:
+ """Penalize relative world-frame yaw rate between two configured bodies."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_rate_difference_l2")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_rate_difference_l2")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ yaw_rate = asset.data.body_ang_vel_w[:, body_id, 2]
+ reference_yaw_rate = reference_asset.data.body_ang_vel_w[:, reference_body_id, 2]
+ return torch.square(yaw_rate - reference_yaw_rate)
+
+
def reward_waist_pos(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
@@ -412,7 +881,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 600
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_speed_stability_v1
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: true
load_run: 2026-07-10_14-48-04_gen2_gait_clearance_stage2
load_checkpoint: model_1998.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.008
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,956 @@
--- git commit ---
83ba64bbb58a02e14483e52adce5f893f3f31cdf
--- git status ---
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scripts/cli_args.py
modified: scripts/play.py
modified: scripts/train.py
modified: source/engineai_lab/tasks/velocity/mdp/__init__.py
modified: source/engineai_lab/tasks/velocity/mdp/observations.py
modified: source/engineai_lab/tasks/velocity/mdp/rewards.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
IsaacLab/
scripts/gen2_check_rl_readiness.py
scripts/gen2_generate_simplified_collisions.py
scripts/gen2_visualize_collisions.py
source/engineai_lab/robots/gen2.py
source/engineai_lab/tasks/velocity/config/gen2/
source/engineai_lab/tasks/velocity/mdp/commands.py
source/engineai_lab/tasks/velocity/mdp/terminations.py
source/gen2_lab/
uv.lock
no changes added to commit (use "git add" and/or "git commit -a")
--- git diff ---
diff --git a/scripts/cli_args.py b/scripts/cli_args.py
index 36f91c5..b4a3257 100644
--- a/scripts/cli_args.py
+++ b/scripts/cli_args.py
@@ -34,6 +34,12 @@ def add_rsl_rl_args(parser: argparse.ArgumentParser):
arg_group.add_argument(
"--wandb_path", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
+ arg_group.add_argument(
+ "--rl_device",
+ type=str,
+ default=None,
+ help="Device used by the RSL-RL policy/optimizer, e.g. cpu, cuda, or cuda:0.",
+ )
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlOnPolicyRunnerCfg:
@@ -77,6 +83,8 @@ def update_rsl_rl_cfg(agent_cfg: RslRlOnPolicyRunnerCfg, args_cli: argparse.Name
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
+ if getattr(args_cli, "rl_device", None) is not None:
+ agent_cfg.device = args_cli.rl_device
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
diff --git a/scripts/play.py b/scripts/play.py
index f19b9cb..1187ff6 100644
--- a/scripts/play.py
+++ b/scripts/play.py
@@ -1,4 +1,4 @@
-"""Script to play a checkpoint if an RL agent from RSL-RL."""
+"""Script to play a checkpoint from an RSL-RL agent."""
"""Launch Isaac Sim Simulator first."""
@@ -17,18 +17,49 @@ if str(REPO_ROOT) not in sys.path:
sys.path.append(str(REPO_ROOT))
# add argparse arguments
-parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.")
+parser = argparse.ArgumentParser(description="Play an RSL-RL policy checkpoint.")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
+parser.add_argument("--max_steps", type=int, default=None, help="Stop replay after this many policy steps.")
+
+command_group = parser.add_argument_group("velocity command", description="Velocity command source during replay.")
+command_group.add_argument(
+ "--command_source",
+ type=str,
+ choices=("random", "fixed", "keyboard"),
+ default="random",
+ help="Use environment-generated, fixed, or keyboard velocity commands.",
+)
+command_group.add_argument("--vx", type=float, default=0.3, help="Fixed forward velocity in m/s.")
+command_group.add_argument("--vy", type=float, default=0.0, help="Fixed lateral velocity in m/s.")
+command_group.add_argument("--wz", type=float, default=0.0, help="Fixed yaw velocity in rad/s.")
+command_group.add_argument(
+ "--command_mode",
+ type=str,
+ choices=("step", "ramp"),
+ default="ramp",
+ help="Apply external commands immediately or through acceleration limits.",
+)
+command_group.add_argument("--linear_accel", type=float, default=0.8, help="Planar acceleration limit in m/s^2.")
+command_group.add_argument("--yaw_accel", type=float, default=1.5, help="Yaw acceleration limit in rad/s^2.")
+command_group.add_argument("--keyboard_vx", type=float, default=0.4, help="Keyboard forward velocity in m/s.")
+command_group.add_argument("--keyboard_vy", type=float, default=0.2, help="Keyboard lateral velocity in m/s.")
+command_group.add_argument("--keyboard_wz", type=float, default=0.5, help="Keyboard yaw velocity in rad/s.")
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
args_cli, hydra_args = parser.parse_known_args()
+if args_cli.linear_accel <= 0.0:
+ parser.error("--linear_accel must be positive.")
+if args_cli.yaw_accel <= 0.0:
+ parser.error("--yaw_accel must be positive.")
+if args_cli.max_steps is not None and args_cli.max_steps <= 0:
+ parser.error("--max_steps must be positive.")
# always enable cameras to record video
# clear out sys.argv for Hydra
sys.argv = [sys.argv[0]] + hydra_args
@@ -39,9 +70,9 @@ simulation_app = app_launcher.app
"""Rest everything follows."""
-import gymnasium as gym
import os
-import pathlib
+
+import gymnasium as gym
import torch
from rsl_rl.runners import OnPolicyRunner
@@ -53,7 +84,6 @@ from isaaclab.envs import (
ManagerBasedRLEnvCfg,
multi_agent_to_single_agent,
)
-from isaaclab.utils.dict import print_dict
from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlVecEnvWrapper
from isaaclab_tasks.utils import get_checkpoint_path
from isaaclab_tasks.utils.hydra import hydra_task_config
@@ -62,6 +92,67 @@ from isaaclab_tasks.utils.hydra import hydra_task_config
import engineai_lab.tasks # noqa: F401
+class VelocityCommandController:
+ """Apply fixed or keyboard commands directly to a velocity command term."""
+
+ def __init__(self, env: RslRlVecEnvWrapper, args: argparse.Namespace):
+ self.env = env
+ self.args = args
+ self.command_term = env.unwrapped.command_manager.get_term("base_velocity")
+ if not hasattr(self.command_term, "vel_command_b"):
+ raise TypeError("The base_velocity command term does not expose a velocity command buffer.")
+
+ self.current_command = torch.zeros(3, device=env.unwrapped.device)
+ self.fixed_command = torch.tensor([args.vx, args.vy, args.wz], device=env.unwrapped.device)
+ self.keyboard = None
+
+ if args.command_source == "keyboard":
+ from isaaclab.devices import Se2Keyboard, Se2KeyboardCfg
+
+ keyboard_cfg = Se2KeyboardCfg(
+ sim_device=str(env.unwrapped.device),
+ v_x_sensitivity=args.keyboard_vx,
+ v_y_sensitivity=args.keyboard_vy,
+ omega_z_sensitivity=args.keyboard_wz,
+ )
+ self.keyboard = Se2Keyboard(keyboard_cfg)
+ print(self.keyboard)
+ else:
+ print(
+ "[INFO] Fixed velocity command: "
+ f"vx={args.vx:.3f} m/s, vy={args.vy:.3f} m/s, wz={args.wz:.3f} rad/s"
+ )
+ print(f"[INFO] External command mode: {args.command_mode}")
+
+ def update(self):
+ target = self.keyboard.advance() if self.keyboard is not None else self.fixed_command
+ if self.args.command_mode == "step":
+ self.current_command.copy_(target)
+ else:
+ self._apply_ramp(target)
+
+ self.command_term.vel_command_b[:] = self.current_command.unsqueeze(0)
+ self.command_term.time_left.fill_(float("inf"))
+ self.command_term.is_standing_env.fill_(False)
+ if hasattr(self.command_term, "is_heading_env"):
+ self.command_term.is_heading_env.fill_(False)
+
+ def _apply_ramp(self, target: torch.Tensor):
+ dt = self.env.unwrapped.step_dt
+ linear_delta = target[:2] - self.current_command[:2]
+ linear_delta_norm = torch.linalg.norm(linear_delta)
+ max_linear_delta = self.args.linear_accel * dt
+ linear_scale = torch.clamp(max_linear_delta / torch.clamp(linear_delta_norm, min=1.0e-6), max=1.0)
+ linear_delta = linear_delta * linear_scale
+ self.current_command[:2] += linear_delta
+
+ yaw_delta = torch.clamp(
+ target[2] - self.current_command[2],
+ min=-self.args.yaw_accel * dt,
+ max=self.args.yaw_accel * dt,
+ )
+ self.current_command[2] += yaw_delta
+
@hydra_task_config(args_cli.task, "rsl_rl_cfg_entry_point")
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlOnPolicyRunnerCfg):
@@ -83,7 +174,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# create isaac environment
env = gym.make(args_cli.task, cfg=env_cfg, render_mode=None)
-
# convert to single-agent instance if required by the RL algorithm
if isinstance(env.unwrapped, DirectMARLEnv):
env = multi_agent_to_single_agent(env)
@@ -91,6 +181,10 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# wrap around environment for rsl-rl
env = RslRlVecEnvWrapper(env)
+ command_controller = None
+ if args_cli.command_source != "random":
+ command_controller = VelocityCommandController(env, args_cli)
+
# load previously trained model
ppo_runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
ppo_runner.load(resume_path)
@@ -103,18 +197,24 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
file_basename = os.path.basename(resume_path).split(".")[0]
ppo_runner.export_policy_to_onnx(path=export_model_dir, filename=file_basename+".onnx")
-
# reset environment
obs = env.get_observations()
- timestep = 0
+ step_count = 0
# simulate environment
- while simulation_app.is_running():
+ while simulation_app.is_running() and (args_cli.max_steps is None or step_count < args_cli.max_steps):
# run everything in inference mode
with torch.inference_mode():
+ if command_controller is not None:
+ command_controller.update()
+ obs = env.get_observations()
# agent stepping
actions = policy(obs)
# env stepping
obs, _, _, _ = env.step(actions)
+ step_count += 1
+
+ if args_cli.max_steps is not None:
+ print(f"[INFO] Replay completed after {step_count} policy steps.")
# close the simulator
env.close()
diff --git a/scripts/train.py b/scripts/train.py
index 01039e3..6f6ffc4 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -22,6 +22,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of enviro
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument("--max_iterations", type=int, default=None, help="RL Policy training iterations.")
+parser.add_argument(
+ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes."
+)
# append RSL-RL cli arguments
cli_args.add_rsl_rl_args(parser)
@@ -81,6 +84,19 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agen
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
+ if args_cli.distributed and args_cli.device is not None and "cpu" in args_cli.device:
+ raise ValueError(
+ "Distributed training is not supported when using CPU device. "
+ "Please use GPU device (e.g. --device cuda) for distributed training."
+ )
+
+ if args_cli.distributed:
+ env_cfg.sim.device = f"cuda:{app_launcher.local_rank}"
+ agent_cfg.device = f"cuda:{app_launcher.local_rank}"
+
+ seed = agent_cfg.seed + app_launcher.local_rank
+ env_cfg.seed = seed
+ agent_cfg.seed = seed
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
diff --git a/source/engineai_lab/tasks/velocity/mdp/__init__.py b/source/engineai_lab/tasks/velocity/mdp/__init__.py
index 6fe10e8..c885999 100644
--- a/source/engineai_lab/tasks/velocity/mdp/__init__.py
+++ b/source/engineai_lab/tasks/velocity/mdp/__init__.py
@@ -1,5 +1,7 @@
from isaaclab_tasks.manager_based.locomotion.velocity.mdp import *
+from .commands import * # noqa: F401, F403
from .rewards import * # noqa: F401, F403
from .observations import * # noqa: F401, F403
from .events import * # noqa: F401, F403
+from .terminations import * # noqa: F401, F403
diff --git a/source/engineai_lab/tasks/velocity/mdp/observations.py b/source/engineai_lab/tasks/velocity/mdp/observations.py
index 0881dba..9dc20ff 100644
--- a/source/engineai_lab/tasks/velocity/mdp/observations.py
+++ b/source/engineai_lab/tasks/velocity/mdp/observations.py
@@ -3,12 +3,30 @@ from __future__ import annotations
import torch
from typing import TYPE_CHECKING
-from isaaclab.utils.math import quat_apply_inverse
+from isaaclab.managers import SceneEntityCfg
+from isaaclab.utils.math import euler_xyz_from_quat, quat_apply_inverse, quat_from_euler_xyz
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedEnv
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
"""Base linear velocity expressed in the base frame."""
asset = env.scene["robot"]
@@ -18,3 +36,22 @@ def robot_base_lin_vel_b(env: ManagerBasedEnv) -> torch.Tensor:
# fallback: rotate world velocity into base frame
return quat_apply_inverse(asset.data.root_quat_w, asset.data.root_lin_vel_w)
+
+def body_ang_vel_yaw_frame(
+ env: ManagerBasedEnv,
+ asset_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Body angular velocity expressed in a yaw-aligned frame for that body."""
+ body_id = _single_body_id(asset_cfg, "body_ang_vel_yaw_frame")
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_ang_vel_w = asset.data.body_ang_vel_w[:, body_id, :]
+ return quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_ang_vel_w)
+
+
+def body_projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor:
+ """Gravity projection in a configured body's local frame."""
+ body_id = _single_body_id(asset_cfg, "body_projected_gravity")
+ asset = env.scene[asset_cfg.name]
+ return quat_apply_inverse(asset.data.body_quat_w[:, body_id, :], asset.data.GRAVITY_VEC_W)
diff --git a/source/engineai_lab/tasks/velocity/mdp/rewards.py b/source/engineai_lab/tasks/velocity/mdp/rewards.py
index 36c3e1c..ec938fe 100644
--- a/source/engineai_lab/tasks/velocity/mdp/rewards.py
+++ b/source/engineai_lab/tasks/velocity/mdp/rewards.py
@@ -10,6 +10,7 @@ from isaaclab.utils.math import (
euler_xyz_from_quat,
quat_apply_inverse,
quat_from_euler_xyz,
+ quat_mul,
quat_rotate_inverse,
wrap_to_pi,
yaw_quat,
@@ -18,6 +19,46 @@ from isaaclab.utils.math import (
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
+
+def _to_env_device(env: ManagerBasedRLEnv, tensor: torch.Tensor) -> torch.Tensor:
+ """Move sensor tensors back to the RL environment device when Isaac uses another CUDA device."""
+ return tensor.to(env.device)
+
+
+def _single_body_id(asset_cfg: SceneEntityCfg, term_name: str) -> int:
+ body_ids = asset_cfg.body_ids
+ if body_ids is None:
+ raise ValueError(f"{term_name} requires asset_cfg with exactly one body name.")
+ if isinstance(body_ids, int):
+ return body_ids
+ if isinstance(body_ids, slice) or len(body_ids) != 1:
+ raise ValueError(f"{term_name} requires exactly one body id, got {body_ids}.")
+ return body_ids[0]
+
+
+def _heading_quat_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ zeros = torch.zeros_like(yaw)
+ return quat_from_euler_xyz(zeros, zeros, yaw - heading_yaw_offset)
+
+
+def _heading_yaw_with_offset(body_quat_w: torch.Tensor, heading_yaw_offset: float = 0.0) -> torch.Tensor:
+ roll, pitch, yaw = euler_xyz_from_quat(body_quat_w)
+ return wrap_to_pi(yaw - heading_yaw_offset)
+
+
+def _command_is_moving(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ commands = env.command_manager.get_command(command_name)
+ return (torch.norm(commands[:, :2], dim=1) > linear_threshold) | (
+ torch.abs(commands[:, 2]) > angular_threshold
+ )
+
+
def action_smoothness(env: ManagerBasedRLEnv) -> torch.Tensor:
"""Penalize action second-order differences to encourage smooth control."""
action_manager = env.action_manager
@@ -90,8 +131,8 @@ def feet_air_time_similarity(
if body_ids is None or len(body_ids) != 2:
raise ValueError("feet_air_time_similarity expects exactly two foot body ids in sensor_cfg.body_ids.")
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, body_ids]
- last_air_time = contact_sensor.data.last_air_time[:, body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, body_ids])
recent_contact = torch.any(first_contact > 0.0, dim=1)
valid = torch.all(last_air_time > min_air_time, dim=1)
@@ -100,6 +141,61 @@ def feet_air_time_similarity(
return reward * (recent_contact & valid)
+def feet_air_time(
+ env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
+) -> torch.Tensor:
+ """Reward long steps while keeping contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ reward = torch.sum((last_air_time - threshold) * first_contact, dim=1)
+ reward *= torch.norm(env.command_manager.get_command(command_name)[:, :2], dim=1) > 0.1
+ return reward
+
+
+def feet_air_time_positive_on_contact(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ sensor_cfg: SceneEntityCfg,
+ min_air_time: float = 0.05,
+ max_air_time: float = 0.25,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward completed swing times without penalizing short exploratory steps."""
+ if max_air_time <= min_air_time:
+ raise ValueError("max_air_time must be greater than min_air_time.")
+
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
+ last_air_time = _to_env_device(env, contact_sensor.data.last_air_time[:, sensor_cfg.body_ids])
+ completed_swing = torch.clamp(last_air_time - min_air_time, min=0.0, max=max_air_time - min_air_time)
+ reward = torch.sum(completed_swing * first_contact, dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
+def feet_air_time_positive_biped(
+ env: ManagerBasedRLEnv,
+ command_name: str,
+ threshold: float,
+ sensor_cfg: SceneEntityCfg,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Dense biped air-time reward with contact-sensor tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ air_time = _to_env_device(env, contact_sensor.data.current_air_time[:, sensor_cfg.body_ids])
+ contact_time = _to_env_device(env, contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids])
+ in_contact = contact_time > 0.0
+ in_mode_time = torch.where(in_contact, contact_time, air_time)
+ single_stance = torch.sum(in_contact.int(), dim=1) == 1
+ reward = torch.min(torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1)[0]
+ reward = torch.clamp(reward, max=threshold)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return reward * moving
+
+
def track_lin_vel_xy_yaw_frame_exp(
env, sigma: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), stand_threshold: float = 0.06
) -> torch.Tensor:
@@ -132,6 +228,79 @@ def track_ang_vel_z_world_exp(
rew_abs = torch.exp(-ang_vel_error_abs * sigma)
return torch.where(stand_command, rew_abs, rew_square)
+
+def track_lin_vel_xy_yaw_frame_exp_body(
+ env,
+ sigma: float,
+ command_name: str,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+) -> torch.Tensor:
+ """Track planar velocity of a configured body in its yaw-aligned frame."""
+ body_id = _single_body_id(asset_cfg, "track_lin_vel_xy_yaw_frame_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_quat_w = asset.data.body_quat_w[:, body_id, :]
+ body_lin_vel_w = asset.data.body_lin_vel_w[:, body_id, :]
+ vel_yaw = quat_apply_inverse(_heading_quat_with_offset(body_quat_w, heading_yaw_offset), body_lin_vel_w)
+ lin_vel_error_square = torch.sum(torch.square(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ lin_vel_error_abs = torch.sum(torch.abs(commands[:, :2] - vel_yaw[:, :2]), dim=1)
+ rew_square = torch.exp(-lin_vel_error_square * sigma)
+ rew_abs = torch.exp(-lin_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_body(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track world-frame yaw angular velocity of a configured body."""
+ body_id = _single_body_id(asset_cfg, "track_ang_vel_z_world_exp_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ ang_vel_error_square = torch.square(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ ang_vel_error_abs = torch.abs(commands[:, 2] - asset.data.body_ang_vel_w[:, body_id, 2])
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
+def track_ang_vel_z_world_exp_bodies(
+ env,
+ command_name: str,
+ sigma: float,
+ asset_cfg: SceneEntityCfg,
+ stand_threshold: float = 0.06,
+) -> torch.Tensor:
+ """Track yaw rate across all configured bodies so internal waist motion cannot satisfy the command alone."""
+ body_ids = asset_cfg.body_ids
+ if body_ids is None or isinstance(body_ids, slice):
+ raise ValueError("track_ang_vel_z_world_exp_bodies requires one or more explicitly resolved body ids.")
+
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ body_yaw_rates = asset.data.body_ang_vel_w[:, body_ids, 2]
+ command_yaw_rate = commands[:, 2].unsqueeze(1)
+ ang_vel_error_square = torch.mean(torch.square(command_yaw_rate - body_yaw_rates), dim=1)
+ ang_vel_error_abs = torch.mean(torch.abs(command_yaw_rate - body_yaw_rates), dim=1)
+ rew_square = torch.exp(-ang_vel_error_square * sigma)
+ rew_abs = torch.exp(-ang_vel_error_abs * sigma)
+ return torch.where(stand_command, rew_abs, rew_square)
+
+
def feet_stumble(
env, sensor_cfg: SceneEntityCfg, tangential_threshold: float = 2.0, normal_threshold: float = 1.0
) -> torch.Tensor:
@@ -141,7 +310,7 @@ def feet_stumble(
below ``normal_threshold``. Returns the count of stumbling feet per environment.
"""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- forces = contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
tangential = torch.norm(forces[..., :2], dim=-1) > tangential_threshold
small_normal = torch.abs(forces[..., 2]) < normal_threshold
stumble = tangential & small_normal
@@ -165,6 +334,7 @@ def feet_contact(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -194,6 +364,7 @@ def feet_contact_fixed(
contact_history = contact_sensor.data.net_forces_w_history
if contact_history is None:
contact_history = contact_sensor.data.net_forces_w.unsqueeze(1)
+ contact_history = _to_env_device(env, contact_history)
contacts = contact_history[:, :, sensor_cfg.body_ids, 2] > force_threshold
contact_num_buf = torch.sum(contacts, dim=-1)
@@ -207,6 +378,68 @@ def feet_contact_fixed(
return reward
+def biped_contact_mode_reward(
+ env: ManagerBasedRLEnv,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward double support while standing and exactly one supporting foot while moving."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ contact_count = torch.sum(contacts.int(), dim=1)
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return torch.where(moving, contact_count == 1, contact_count == 2).float()
+
+
+def swing_foot_clearance_reward(
+ env: ManagerBasedRLEnv,
+ asset_cfg: SceneEntityCfg,
+ sensor_cfg: SceneEntityCfg,
+ command_name: str,
+ target_height: float,
+ std: float,
+ force_threshold: float = 5.0,
+ linear_threshold: float = 0.1,
+ angular_threshold: float = 0.1,
+) -> torch.Tensor:
+ """Reward swing-foot height relative to the supporting foot."""
+ if std <= 0.0:
+ raise ValueError("std must be positive.")
+
+ asset = env.scene[asset_cfg.name]
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ forces = _to_env_device(env, contact_sensor.data.net_forces_w[:, sensor_cfg.body_ids, :])
+ contacts = torch.norm(forces, dim=-1) > force_threshold
+ single_stance = torch.sum(contacts.int(), dim=1) == 1
+ swing_feet = ~contacts
+
+ foot_height = asset.data.body_pos_w[:, asset_cfg.body_ids, 2]
+ if foot_height.shape[1] != contacts.shape[1]:
+ raise ValueError("asset_cfg and sensor_cfg must resolve the same number of feet.")
+ stance_height = torch.sum(foot_height * contacts, dim=1, keepdim=True)
+ swing_clearance = foot_height - stance_height
+ clearance_reward = torch.exp(-torch.square(swing_clearance - target_height) / (std * std))
+ clearance_reward = torch.sum(clearance_reward * swing_feet, dim=1)
+
+ moving = _command_is_moving(env, command_name, linear_threshold, angular_threshold)
+ return clearance_reward * single_stance * moving
+
+
+def feet_slide(env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
+ """Penalize foot sliding while keeping contact tensors on the env device."""
+ contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
+ asset = env.scene[asset_cfg.name]
+ body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
+ return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)
+
+
def feet_position(env,
asset_cfg: SceneEntityCfg,
@@ -253,6 +486,43 @@ def feet_position(env,
return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+def feet_position_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ desired_foot_positions: tuple[tuple[float, float, float], ...],
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ scale: float = 3.0,
+) -> torch.Tensor:
+ """Reward standing foot positions relative to a configured reference body."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_position_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ stand_command = (torch.norm(commands[:, :2], dim=1) < stand_threshold) & (
+ torch.abs(commands[:, 2]) < stand_threshold
+ )
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = asset.data.body_pos_w[:, asset_cfg.body_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_pos_w.shape
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat_per_foot = heading_quat.unsqueeze(1).expand(-1, num_feet, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat_per_foot, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, num_feet, 3)
+
+ desired = torch.tensor(desired_foot_positions, dtype=feet_pos_heading.dtype, device=feet_pos_heading.device)
+ if desired.shape != (num_feet, 3):
+ raise ValueError(f"desired_foot_positions must have shape ({num_feet}, 3), got {tuple(desired.shape)}.")
+ position_error = torch.sum(torch.abs(feet_pos_heading - desired.unsqueeze(0)), dim=(1, 2))
+ reward_stand = torch.exp(-position_error * scale)
+ return torch.where(stand_command, reward_stand, torch.ones_like(reward_stand))
+
+
def feet_regulation(
env,
asset_cfg: SceneEntityCfg,
@@ -293,10 +563,10 @@ def feet_landing_velocity(
) -> torch.Tensor:
"""Penalize high downward landing speed at first contact to reduce impact noise."""
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- first_contact = contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids]
+ first_contact = _to_env_device(env, contact_sensor.compute_first_contact(env.step_dt)[:, sensor_cfg.body_ids])
asset = env.scene[asset_cfg.name]
- foot_vel_z = asset.data.body_lin_vel_w[:, sensor_cfg.body_ids, 2]
+ foot_vel_z = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, 2]
landing_speed = torch.clamp(-foot_vel_z - velocity_threshold, min=0.0)
penalty = torch.sum(torch.pow(landing_speed, power) * first_contact, dim=1)
return penalty
@@ -350,6 +620,117 @@ def base_height_tracking(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"
height_error = torch.abs(asset.data.root_pos_w[:, 2] - target_height)
return torch.exp(-height_error * 30.0)
+
+def body_height_tracking(
+ env,
+ asset_cfg: SceneEntityCfg,
+ target_height: float,
+ scale: float = 30.0,
+) -> torch.Tensor:
+ """Reward keeping a configured body height near a target height."""
+ body_id = _single_body_id(asset_cfg, "body_height_tracking")
+ asset = env.scene[asset_cfg.name]
+ height_error = torch.abs(asset.data.body_pos_w[:, body_id, 2] - target_height)
+ return torch.exp(-height_error * scale)
+
+
+def body_vertical_velocity_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ deadband: float = 0.0,
+) -> torch.Tensor:
+ """Penalize vertical body velocity outside a small natural-motion deadband."""
+ body_id = _single_body_id(asset_cfg, "body_vertical_velocity_l2")
+ asset = env.scene[asset_cfg.name]
+ vertical_speed = torch.abs(asset.data.body_lin_vel_w[:, body_id, 2])
+ return torch.square(torch.clamp(vertical_speed - deadband, min=0.0))
+
+
+def body_roll_pitch_ang_vel_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ deadband: float = 0.0,
+) -> torch.Tensor:
+ """Penalize horizontal angular speed while allowing normal gait oscillation."""
+ body_id = _single_body_id(asset_cfg, "body_roll_pitch_ang_vel_l2")
+ asset = env.scene[asset_cfg.name]
+ horizontal_ang_speed = torch.linalg.norm(asset.data.body_ang_vel_w[:, body_id, :2], dim=1)
+ return torch.square(torch.clamp(horizontal_ang_speed - deadband, min=0.0))
+
+
+def cross_body_arm_swing_reward(
+ env,
+ arm_asset_cfg: SceneEntityCfg,
+ feet_asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ heading_yaw_offset: float = 0.0,
+ min_forward_speed: float = 0.1,
+ full_swing_speed: float = 0.6,
+ phase_distance: float = 0.25,
+ shoulder_amplitude: float = 0.18,
+ elbow_flexion: float = 0.15,
+ shoulder_phase_signs: tuple[float, float] = (-1.0, -1.0),
+ elbow_flexion_signs: tuple[float, float] = (1.0, -1.0),
+ std: float = 0.2,
+) -> torch.Tensor:
+ """Track speed-scaled shoulder and elbow targets that oppose the leg phase.
+
+ Arm joints must be ordered as left/right shoulder pitch followed by left/right elbow flexion.
+ Feet must be ordered left then right.
+ """
+ if full_swing_speed <= min_forward_speed:
+ raise ValueError("full_swing_speed must be greater than min_forward_speed.")
+ if phase_distance <= 0.0 or std <= 0.0:
+ raise ValueError("phase_distance and std must be positive.")
+
+ joint_ids = arm_asset_cfg.joint_ids
+ foot_ids = feet_asset_cfg.body_ids
+ if joint_ids is None or isinstance(joint_ids, slice) or len(joint_ids) != 4:
+ raise ValueError("cross_body_arm_swing_reward requires four ordered arm joint ids.")
+ if foot_ids is None or isinstance(foot_ids, slice) or len(foot_ids) != 2:
+ raise ValueError("cross_body_arm_swing_reward requires two ordered foot body ids.")
+
+ reference_body_id = _single_body_id(reference_body_cfg, "cross_body_arm_swing_reward")
+ asset = env.scene[arm_asset_cfg.name]
+ feet_asset = env.scene[feet_asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+
+ feet_pos_w = feet_asset.data.body_pos_w[:, foot_ids, :]
+ reference_pos_w = reference_asset.data.body_pos_w[:, reference_body_id, :]
+ reference_quat_w = reference_asset.data.body_quat_w[:, reference_body_id, :]
+ feet_pos_rel = feet_pos_w - reference_pos_w.unsqueeze(1)
+
+ num_envs = feet_pos_w.shape[0]
+ heading_quat = _heading_quat_with_offset(reference_quat_w, heading_yaw_offset)
+ heading_quat = heading_quat.unsqueeze(1).expand(-1, 2, -1).reshape(-1, 4)
+ feet_pos_heading = quat_apply_inverse(heading_quat, feet_pos_rel.reshape(-1, 3)).reshape(num_envs, 2, 3)
+ leg_phase = torch.clamp(
+ (feet_pos_heading[:, 0, 0] - feet_pos_heading[:, 1, 0]) / phase_distance,
+ min=-1.0,
+ max=1.0,
+ )
+
+ commands = env.command_manager.get_command(command_name)
+ speed_scale = torch.clamp(
+ (torch.abs(commands[:, 0]) - min_forward_speed) / (full_swing_speed - min_forward_speed),
+ min=0.0,
+ max=1.0,
+ )
+
+ joint_pos = asset.data.joint_pos[:, joint_ids]
+ target_pos = asset.data.default_joint_pos[:, joint_ids].clone()
+ shoulder_signs = joint_pos.new_tensor(shoulder_phase_signs)
+ elbow_signs = joint_pos.new_tensor(elbow_flexion_signs)
+ target_pos[:, :2] += (
+ shoulder_amplitude * speed_scale * leg_phase
+ ).unsqueeze(1) * shoulder_signs.unsqueeze(0)
+ target_pos[:, 2:] += (elbow_flexion * speed_scale).unsqueeze(1) * elbow_signs.unsqueeze(0)
+
+ mean_square_error = torch.mean(torch.square(joint_pos - target_pos), dim=1)
+ return torch.exp(-mean_square_error / (std * std))
+
+
def energy_cost(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Penalize energy consumption approximated by the sum of squared joint torques."""
asset = env.scene[asset_cfg.name]
@@ -385,6 +766,50 @@ def feet_orientation(env, asset_cfg: SceneEntityCfg, command_name: str, stand_th
return torch.exp(-rew * 2.0)
+def feet_orientation_relative_to_body(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ command_name: str,
+ stand_threshold: float = 0.06,
+ heading_yaw_offset: float = 0.0,
+ foot_frame_offsets_rpy: tuple[tuple[float, float, float], ...] | None = None,
+ scale: float = 2.0,
+) -> torch.Tensor:
+ """Reward physical sole orientation relative to a configured reference body's heading."""
+ reference_body_id = _single_body_id(reference_body_cfg, "feet_orientation_relative_to_body")
+ commands = env.command_manager.get_command(command_name)
+ yaw_command = torch.abs(commands[:, 2]) > stand_threshold
+
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ feet_quat = asset.data.body_quat_w[:, asset_cfg.body_ids, :]
+ reference_quat = reference_asset.data.body_quat_w[:, reference_body_id, :]
+
+ num_envs, num_feet, _ = feet_quat.shape
+ feet_flat = feet_quat.reshape(-1, 4)
+ if foot_frame_offsets_rpy is not None:
+ offsets = torch.tensor(foot_frame_offsets_rpy, dtype=feet_quat.dtype, device=feet_quat.device)
+ if offsets.shape != (num_feet, 3):
+ raise ValueError(f"foot_frame_offsets_rpy must have shape ({num_feet}, 3), got {tuple(offsets.shape)}.")
+ offset_quat = quat_from_euler_xyz(offsets[:, 0], offsets[:, 1], offsets[:, 2])
+ offset_quat = offset_quat.unsqueeze(0).expand(num_envs, -1, -1).reshape(-1, 4)
+ feet_flat = quat_mul(feet_flat, offset_quat)
+
+ roll, pitch, yaw = euler_xyz_from_quat(feet_flat)
+ roll = roll.reshape(num_envs, num_feet)
+ pitch = pitch.reshape(num_envs, num_feet)
+ yaw = yaw.reshape(num_envs, num_feet)
+
+ reference_yaw = _heading_yaw_with_offset(reference_quat, heading_yaw_offset)
+ feet_roll_pitch_error = torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1)
+ feet_yaw_error = torch.abs(wrap_to_pi(yaw - reference_yaw.unsqueeze(1)))
+
+ rew = torch.sum(feet_roll_pitch_error + feet_yaw_error, dim=1)
+ rew[yaw_command] = torch.sum(feet_roll_pitch_error[yaw_command], dim=1)
+ return torch.exp(-rew * scale)
+
+
def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Reward keeping the base roll/pitch near zero."""
asset = env.scene[asset_cfg.name]
@@ -392,6 +817,50 @@ def base_orientation(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -
base_euler = torch.stack((roll, pitch, yaw), dim=-1)
return torch.exp(-torch.sum(torch.abs(base_euler[:, :2]), dim=-1) * 10.0)
+
+def body_orientation(env, asset_cfg: SceneEntityCfg, scale: float = 10.0) -> torch.Tensor:
+ """Reward keeping a configured body's roll/pitch near zero."""
+ body_id = _single_body_id(asset_cfg, "body_orientation")
+ asset = env.scene[asset_cfg.name]
+ roll, pitch, yaw = euler_xyz_from_quat(asset.data.body_quat_w[:, body_id, :])
+ return torch.exp(-torch.sum(torch.abs(torch.stack((roll, pitch), dim=-1)), dim=-1) * scale)
+
+
+def body_yaw_alignment(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+ heading_yaw_offset: float = 0.0,
+ reference_heading_yaw_offset: float = 0.0,
+ scale: float = 4.0,
+) -> torch.Tensor:
+ """Reward keeping one body's heading aligned with another body's heading."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_alignment")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_alignment")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ body_yaw = _heading_yaw_with_offset(asset.data.body_quat_w[:, body_id, :], heading_yaw_offset)
+ reference_yaw = _heading_yaw_with_offset(
+ reference_asset.data.body_quat_w[:, reference_body_id, :], reference_heading_yaw_offset
+ )
+ return torch.exp(-torch.abs(wrap_to_pi(body_yaw - reference_yaw)) * scale)
+
+
+def body_yaw_rate_difference_l2(
+ env,
+ asset_cfg: SceneEntityCfg,
+ reference_body_cfg: SceneEntityCfg,
+) -> torch.Tensor:
+ """Penalize relative world-frame yaw rate between two configured bodies."""
+ body_id = _single_body_id(asset_cfg, "body_yaw_rate_difference_l2")
+ reference_body_id = _single_body_id(reference_body_cfg, "body_yaw_rate_difference_l2")
+ asset = env.scene[asset_cfg.name]
+ reference_asset = env.scene[reference_body_cfg.name]
+ yaw_rate = asset.data.body_ang_vel_w[:, body_id, 2]
+ reference_yaw_rate = reference_asset.data.body_ang_vel_w[:, reference_body_id, 2]
+ return torch.square(yaw_rate - reference_yaw_rate)
+
+
def reward_waist_pos(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
@@ -412,7 +881,9 @@ def reward_waist_pos(
def penalize_foot_stumble(env, sensor_cfg: SceneEntityCfg, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name]
- contacts = contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0] > 1.0
+ contacts = _to_env_device(
+ env, contact_sensor.data.net_forces_w_history[:, :, sensor_cfg.body_ids, :].norm(dim=-1).max(dim=1)[0]
+ ) > 1.0
asset = env.scene[asset_cfg.name]
body_vel = asset.data.body_lin_vel_w[:, asset_cfg.body_ids, :2]
return torch.sum(body_vel.norm(dim=-1) * contacts, dim=1)

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 400
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_speed_flat_consolidation_v2
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: true
load_run: 2026-07-10_16-24-27_gen2_speed_stability_v1
load_checkpoint: model_2597.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.004
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

View File

@ -0,0 +1,63 @@
seed: 42
device: cuda:0
num_steps_per_env: 24
max_iterations: 300
empirical_normalization: {}
obs_groups:
actor:
- policy
critic:
- policy
clip_actions: null
check_for_nan: true
save_interval: 50
experiment_name: velocity_flat_terrain_gen2
run_name: gen2_natural_arm_swing_stage3_v1
logger: tensorboard
neptune_project: isaaclab
wandb_project: isaaclab
resume: true
load_run: 2026-07-13_09-55-02_gen2_speed_flat_consolidation_v2
load_checkpoint: model_2996.pt
class_name: OnPolicyRunner
actor:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg:
class_name: GaussianDistribution
init_std: 1.0
std_type: scalar
critic:
class_name: MLPModel
hidden_dims:
- 512
- 256
- 128
activation: elu
obs_normalization: true
distribution_cfg: null
algorithm:
class_name: PPO
num_learning_epochs: 5
num_mini_batches: 4
learning_rate: 0.001
schedule: adaptive
gamma: 0.99
lam: 0.95
entropy_coef: 0.005
desired_kl: 0.01
max_grad_norm: 1.0
optimizer: adam
value_loss_coef: 1.0
use_clipped_value_loss: true
clip_param: 0.2
normalize_advantage_per_mini_batch: false
share_cnn_encoders: false
rnd_cfg: null
symmetry_cfg: null
policy: {}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/10-41-59
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/11-19-57
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/11-20-11
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/11-22-15
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[2026-07-09 11:22:15,376][isaaclab.envs.manager_based_env][WARNING] - Seed not set for the environment. The environment creation may not be deterministic.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/11-31-08
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-31-13
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-32-03
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[2026-07-09 12:32:03,666][isaaclab.envs.manager_based_env][WARNING] - Seed not set for the environment. The environment creation may not be deterministic.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-32-50
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[2026-07-09 12:32:50,413][isaaclab.envs.manager_based_env][WARNING] - Seed not set for the environment. The environment creation may not be deterministic.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-36-35
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[2026-07-09 12:36:35,567][isaaclab.envs.manager_based_env][WARNING] - Seed not set for the environment. The environment creation may not be deterministic.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-38-06
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[2026-07-09 12:38:06,814][isaaclab.envs.manager_based_env][WARNING] - Seed not set for the environment. The environment creation may not be deterministic.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-AMP-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-42-45
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-AMP-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/12-43-22
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
hydra:
run:
dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra.job.num}
launcher:
_target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher
sweeper:
_target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper
max_batch_size: null
params: null
help:
app_name: ${hydra.job.name}
header: '${hydra.help.app_name} is powered by Hydra.
'
footer: 'Powered by Hydra (https://hydra.cc)
Use --hydra-help to view Hydra specific help
'
template: '${hydra.help.header}
== Configuration groups ==
Compose your configuration from those groups (group=option)
$APP_CONFIG_GROUPS
== Config ==
Override anything in the config (foo.bar=value)
$CONFIG
${hydra.help.footer}
'
hydra_help:
template: 'Hydra (${hydra.runtime.version})
See https://hydra.cc for more info.
== Flags ==
$FLAGS_HELP
== Configuration groups ==
Compose your configuration from those groups (For example, append hydra/job_logging=disabled
to command line)
$HYDRA_CONFIG_GROUPS
Use ''--cfg hydra'' to Show the Hydra config.
'
hydra_help: ???
hydra_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][HYDRA] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
root:
level: INFO
handlers:
- console
loggers:
logging_example:
level: DEBUG
disable_existing_loggers: false
job_logging:
version: 1
formatters:
simple:
format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: simple
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log
root:
level: INFO
handlers:
- console
- file
disable_existing_loggers: false
env: {}
mode: RUN
searchpath: []
callbacks: {}
output_subdir: .hydra
overrides:
hydra:
- hydra.mode=RUN
task: []
job:
name: hydra
chdir: null
override_dirname: ''
id: ???
num: ???
config_name: Flat-AMP-PM01-v0
env_set: {}
env_copy: []
config:
override_dirname:
kv_sep: '='
item_sep: ','
exclude_keys: []
runtime:
version: 1.3.4
version_base: '1.3'
cwd: /home/xtkuang/Projects/cmvr/RL/engineai_amp
config_sources:
- path: hydra.conf
schema: pkg
provider: hydra
- path: isaaclab_tasks.utils
schema: pkg
provider: main
- path: ''
schema: structured
provider: schema
output_dir: /home/xtkuang/Projects/cmvr/RL/engineai_amp/outputs/2026-07-09/15-26-24
choices:
hydra/env: default
hydra/callbacks: null
hydra/job_logging: default
hydra/hydra_logging: default
hydra/hydra_help: default
hydra/help: default
hydra/sweeper: basic
hydra/launcher: basic
hydra/output: default
verbose: false

View File

@ -0,0 +1 @@
[]

Some files were not shown because too many files have changed in this diff Show More