upload checkpoints
This commit is contained in:
parent
b989c02b1b
commit
a80c681381
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,7 +3,5 @@
|
||||
build/
|
||||
*.egg-info/
|
||||
*__pycache__/
|
||||
*logs/
|
||||
*outputs/
|
||||
dataset/
|
||||
models/
|
||||
models/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
Binary file not shown.
@ -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: {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
1683
outputs/2026-07-09/10-41-59/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/10-41-59/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/10-41-59/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/10-41-59/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/10-41-59/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/10-41-59/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/10-41-59/hydra.log
Normal file
0
outputs/2026-07-09/10-41-59/hydra.log
Normal file
1683
outputs/2026-07-09/11-19-57/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/11-19-57/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/11-19-57/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/11-19-57/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/11-19-57/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/11-19-57/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/11-19-57/hydra.log
Normal file
0
outputs/2026-07-09/11-19-57/hydra.log
Normal file
1683
outputs/2026-07-09/11-20-11/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/11-20-11/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/11-20-11/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/11-20-11/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/11-20-11/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/11-20-11/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/11-20-11/hydra.log
Normal file
0
outputs/2026-07-09/11-20-11/hydra.log
Normal file
1683
outputs/2026-07-09/11-22-15/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/11-22-15/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/11-22-15/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/11-22-15/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/11-22-15/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/11-22-15/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
1
outputs/2026-07-09/11-22-15/hydra.log
Normal file
1
outputs/2026-07-09/11-22-15/hydra.log
Normal 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.
|
||||
1683
outputs/2026-07-09/11-31-08/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/11-31-08/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/11-31-08/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/11-31-08/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/11-31-08/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/11-31-08/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/11-31-08/hydra.log
Normal file
0
outputs/2026-07-09/11-31-08/hydra.log
Normal file
1683
outputs/2026-07-09/12-31-13/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/12-31-13/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-31-13/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-31-13/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-31-13/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-31-13/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/12-31-13/hydra.log
Normal file
0
outputs/2026-07-09/12-31-13/hydra.log
Normal file
1683
outputs/2026-07-09/12-32-03/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/12-32-03/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-32-03/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-32-03/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-32-03/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-32-03/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
1
outputs/2026-07-09/12-32-03/hydra.log
Normal file
1
outputs/2026-07-09/12-32-03/hydra.log
Normal 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.
|
||||
1683
outputs/2026-07-09/12-32-50/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/12-32-50/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-32-50/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-32-50/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-32-50/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-32-50/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
1
outputs/2026-07-09/12-32-50/hydra.log
Normal file
1
outputs/2026-07-09/12-32-50/hydra.log
Normal 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.
|
||||
1683
outputs/2026-07-09/12-36-35/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/12-36-35/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-36-35/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-36-35/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-36-35/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-36-35/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
1
outputs/2026-07-09/12-36-35/hydra.log
Normal file
1
outputs/2026-07-09/12-36-35/hydra.log
Normal 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.
|
||||
1683
outputs/2026-07-09/12-38-06/.hydra/config.yaml
Normal file
1683
outputs/2026-07-09/12-38-06/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-38-06/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-38-06/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-38-06/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-38-06/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
1
outputs/2026-07-09/12-38-06/hydra.log
Normal file
1
outputs/2026-07-09/12-38-06/hydra.log
Normal 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.
|
||||
1699
outputs/2026-07-09/12-42-45/.hydra/config.yaml
Normal file
1699
outputs/2026-07-09/12-42-45/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-42-45/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-42-45/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-42-45/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-42-45/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/12-42-45/hydra.log
Normal file
0
outputs/2026-07-09/12-42-45/hydra.log
Normal file
1699
outputs/2026-07-09/12-43-22/.hydra/config.yaml
Normal file
1699
outputs/2026-07-09/12-43-22/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/12-43-22/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/12-43-22/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/12-43-22/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/12-43-22/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
0
outputs/2026-07-09/12-43-22/hydra.log
Normal file
0
outputs/2026-07-09/12-43-22/hydra.log
Normal file
1699
outputs/2026-07-09/15-26-24/.hydra/config.yaml
Normal file
1699
outputs/2026-07-09/15-26-24/.hydra/config.yaml
Normal file
File diff suppressed because it is too large
Load Diff
154
outputs/2026-07-09/15-26-24/.hydra/hydra.yaml
Normal file
154
outputs/2026-07-09/15-26-24/.hydra/hydra.yaml
Normal 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
|
||||
1
outputs/2026-07-09/15-26-24/.hydra/overrides.yaml
Normal file
1
outputs/2026-07-09/15-26-24/.hydra/overrides.yaml
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user