feat: add smooth/bridge tasks and fire ant stability training docs
- register Smooth/Bridge M20 tasks and add env configs - add smooth random terrain and roll/pitch orientation rewards - update M20 URDF mass and wheel geometry, refresh prototype specs - add fire ant smooth training task document
This commit is contained in:
@@ -65,6 +65,7 @@ DEEPROBOTICS_LITE3_CFG = ArticulationCfg(
|
||||
DEEPROBOTICS_M20_CFG = ArticulationCfg(
|
||||
spawn=sim_utils.UrdfFileCfg(
|
||||
asset_path=f"{ISAACLAB_ASSETS_DATA_DIR}/M20/M20_urdf/urdf/Solenopsis.urdf",
|
||||
usd_dir="/home/suyufei/tmp/IsaacLab_m20_wheel220",
|
||||
fix_base=False,
|
||||
merge_fixed_joints=True,
|
||||
joint_drive=None,
|
||||
@@ -88,7 +89,7 @@ DEEPROBOTICS_M20_CFG = ArticulationCfg(
|
||||
),
|
||||
),
|
||||
init_state=ArticulationCfg.InitialStateCfg(
|
||||
pos=(0.0, 0.0, 0.306),
|
||||
pos=(0.0, 0.0, 0.32),
|
||||
joint_pos={
|
||||
"f[l,r]_knee_joint": 0.5,
|
||||
"h[l,r]_knee_joint": -0.5,
|
||||
|
||||
+22
@@ -44,3 +44,25 @@ gym.register(
|
||||
"cusrl_cfg_entry_point": f"{agents.__name__}.cusrl_ppo_cfg:DeeproboticsM20RoughTrainerCfg",
|
||||
},
|
||||
)
|
||||
|
||||
gym.register(
|
||||
id="Rough-Deeprobotics-M20-Bridge-v0",
|
||||
entry_point="isaaclab.envs:ManagerBasedRLEnv",
|
||||
disable_env_checker=True,
|
||||
kwargs={
|
||||
"env_cfg_entry_point": f"{__name__}.bridge_env_cfg:DeeproboticsM20BridgeEnvCfg",
|
||||
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:DeeproboticsM20BridgePPORunnerCfg",
|
||||
"cusrl_cfg_entry_point": f"{agents.__name__}.cusrl_ppo_cfg:DeeproboticsM20RoughTrainerCfg",
|
||||
},
|
||||
)
|
||||
|
||||
gym.register(
|
||||
id="Smooth-Deeprobotics-M20-v0",
|
||||
entry_point="isaaclab.envs:ManagerBasedRLEnv",
|
||||
disable_env_checker=True,
|
||||
kwargs={
|
||||
"env_cfg_entry_point": f"{__name__}.smooth_env_cfg:DeeproboticsM20SmoothEnvCfg",
|
||||
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:DeeproboticsM20SmoothPPORunnerCfg",
|
||||
"cusrl_cfg_entry_point": f"{agents.__name__}.cusrl_ppo_cfg:DeeproboticsM20RoughTrainerCfg",
|
||||
},
|
||||
)
|
||||
|
||||
+22
@@ -54,3 +54,25 @@ class DeeproboticsM20SupportRoughPPORunnerCfg(DeeproboticsM20RoughPPORunnerCfg):
|
||||
super().__post_init__()
|
||||
|
||||
self.experiment_name = "deeprobotics_m20_support"
|
||||
|
||||
|
||||
@configclass
|
||||
class DeeproboticsM20BridgePPORunnerCfg(DeeproboticsM20RoughPPORunnerCfg):
|
||||
"""Single-side bridge fine-tuning, forward-only command."""
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
|
||||
self.max_iterations = 5000
|
||||
self.experiment_name = "huoyi_bridge"
|
||||
|
||||
|
||||
@configclass
|
||||
class DeeproboticsM20SmoothPPORunnerCfg(DeeproboticsM20RoughPPORunnerCfg):
|
||||
"""Smooth random-undulation fine-tuning, forward-only command."""
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
|
||||
self.max_iterations = 5000
|
||||
self.experiment_name = "huoyi_smooth"
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) 2025 Deep Robotics
|
||||
# SPDX-License-Identifier: BSD 3-Clause
|
||||
|
||||
"""Single-side bridge task: left legs on a long raised bridge, right legs on flat ground."""
|
||||
|
||||
from isaaclab.terrains import TerrainGeneratorCfg
|
||||
from isaaclab.utils import configclass
|
||||
|
||||
import rl_training.tasks.manager_based.locomotion.velocity.mdp as mdp
|
||||
|
||||
from .rough_env_cfg import DeeproboticsM20RoughEnvCfg
|
||||
|
||||
|
||||
@configclass
|
||||
class DeeproboticsM20BridgeEnvCfg(DeeproboticsM20RoughEnvCfg):
|
||||
"""Stage 1 policy on a long single-side bridge, forward-only command."""
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
|
||||
# ------------------------------Terrain------------------------------
|
||||
# One long bridge cell type. The bridge runs along +x, so the robot can
|
||||
# keep walking forward without leaving the split-level configuration.
|
||||
self.scene.terrain.terrain_generator = TerrainGeneratorCfg(
|
||||
size=(12.0, 4.0),
|
||||
border_width=2.0,
|
||||
num_rows=10,
|
||||
num_cols=20,
|
||||
horizontal_scale=0.1,
|
||||
vertical_scale=0.005,
|
||||
slope_threshold=0.75,
|
||||
use_cache=False,
|
||||
curriculum=True,
|
||||
sub_terrains={
|
||||
"single_side_bridge": mdp.SingleSideBridgeTerrainCfg(
|
||||
proportion=1.0,
|
||||
bridge_height_range=(0.06, 0.14),
|
||||
bridge_width=1.5,
|
||||
)
|
||||
},
|
||||
)
|
||||
self.scene.terrain.max_init_terrain_level = 0
|
||||
|
||||
# ------------------------------Spawn------------------------------
|
||||
# Spawn exactly on the bridge edge: y=0 and yaw=0 put the left wheels
|
||||
# on the bridge and the right wheels on the flat ground.
|
||||
self.events.randomize_reset_base.params = {
|
||||
"pose_range": {
|
||||
"x": (-1.0, 1.0),
|
||||
"y": (0.0, 0.0),
|
||||
"z": (0.30, 0.30),
|
||||
"roll": (-0.03, 0.03),
|
||||
"pitch": (-0.03, 0.03),
|
||||
"yaw": (0.0, 0.0),
|
||||
},
|
||||
"velocity_range": {
|
||||
"x": (-0.1, 0.1),
|
||||
"y": (0.0, 0.0),
|
||||
"z": (0.0, 0.0),
|
||||
"roll": (0.0, 0.0),
|
||||
"pitch": (0.0, 0.0),
|
||||
"yaw": (0.0, 0.0),
|
||||
},
|
||||
}
|
||||
|
||||
# ------------------------------Commands------------------------------
|
||||
# Forward-only: no lateral and no yaw command.
|
||||
self.commands.base_velocity.heading_command = False
|
||||
self.commands.base_velocity.rel_standing_envs = 0.0
|
||||
self.commands.base_velocity.rel_heading_envs = 0.0
|
||||
self.commands.base_velocity.ranges.lin_vel_x = (0.2, 0.8)
|
||||
self.commands.base_velocity.ranges.lin_vel_y = (0.0, 0.0)
|
||||
self.commands.base_velocity.ranges.ang_vel_z = (0.0, 0.0)
|
||||
|
||||
# Keep the same 33-dim policy / 8-dim action layout as the other tasks.
|
||||
self.disable_zero_weight_rewards()
|
||||
+39
-68
@@ -7,8 +7,8 @@
|
||||
from isaaclab.managers import CurriculumTermCfg as CurrTerm
|
||||
from isaaclab.managers import EventTermCfg as EventTerm
|
||||
from isaaclab.managers import RewardTermCfg as RewTerm
|
||||
from isaaclab.managers import ObservationTermCfg as ObsTerm
|
||||
from isaaclab.managers import SceneEntityCfg
|
||||
from isaaclab.managers import TerminationTermCfg as DoneTerm
|
||||
from isaaclab.utils import configclass
|
||||
|
||||
import rl_training.tasks.manager_based.locomotion.velocity.mdp as mdp
|
||||
@@ -92,7 +92,7 @@ class DeeproboticsM20RewardsCfg(RewardsCfg):
|
||||
params={
|
||||
"command_name": "base_velocity",
|
||||
"std": 0.6,
|
||||
"wheel_radius": 0.096,
|
||||
"wheel_radius": 0.11,
|
||||
"track_width": 0.50,
|
||||
"left_wheel_indices": (0, 2),
|
||||
"right_wheel_indices": (1, 3),
|
||||
@@ -132,7 +132,7 @@ class DeeproboticsM20RewardsCfg(RewardsCfg):
|
||||
"wheel_sensor_cfg": SceneEntityCfg("contact_forces", body_names=""),
|
||||
"asset_cfg": SceneEntityCfg("robot"),
|
||||
"wheel_asset_cfg": SceneEntityCfg("robot", joint_names=""),
|
||||
"wheel_radius": 0.096,
|
||||
"wheel_radius": 0.11,
|
||||
"support_force_threshold": 15.0,
|
||||
"wheel_obstacle_force_threshold": 35.0,
|
||||
"max_contact_time": 0.45,
|
||||
@@ -161,6 +161,16 @@ class DeeproboticsM20RewardsCfg(RewardsCfg):
|
||||
},
|
||||
)
|
||||
|
||||
knee_inward_fold_penalty = RewTerm(
|
||||
func=mdp.knee_inward_fold_penalty,
|
||||
weight=0.0,
|
||||
params={
|
||||
"front_asset_cfg": SceneEntityCfg("robot", joint_names=""),
|
||||
"hind_asset_cfg": SceneEntityCfg("robot", joint_names=""),
|
||||
"max_inward_tilt": 0.0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@configclass
|
||||
class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
@@ -188,6 +198,8 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
knee_joint_names = [
|
||||
"fl_knee_joint", "fr_knee_joint", "hl_knee_joint", "hr_knee_joint",
|
||||
]
|
||||
front_knee_joint_names = ["fl_knee_joint", "fr_knee_joint"]
|
||||
hind_knee_joint_names = ["hl_knee_joint", "hr_knee_joint"]
|
||||
joint_names = leg_joint_names + wheel_joint_names
|
||||
# fmt: on
|
||||
|
||||
@@ -219,63 +231,6 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
self.observations.policy.height_scan = None
|
||||
self.observations.policy.joint_pos.params["asset_cfg"].joint_names = self.joint_names
|
||||
self.observations.policy.joint_vel.params["asset_cfg"].joint_names = self.joint_names
|
||||
self.observations.policy.wheel_contact = ObsTerm(
|
||||
func=mdp.contact_state,
|
||||
params={
|
||||
"sensor_cfg": SceneEntityCfg("contact_forces", body_names=[self.foot_link_name]),
|
||||
"threshold": 2.0,
|
||||
},
|
||||
clip=(0.0, 1.0),
|
||||
scale=1.0,
|
||||
)
|
||||
self.observations.policy.support_contact = ObsTerm(
|
||||
func=mdp.contact_state,
|
||||
params={
|
||||
"sensor_cfg": SceneEntityCfg("contact_forces", body_names=self.support_link_names),
|
||||
"threshold": 2.0,
|
||||
},
|
||||
clip=(0.0, 1.0),
|
||||
scale=1.0,
|
||||
)
|
||||
self.observations.policy.wheel_contact_force = ObsTerm(
|
||||
func=mdp.contact_force_norm,
|
||||
params={
|
||||
"sensor_cfg": SceneEntityCfg("contact_forces", body_names=[self.foot_link_name]),
|
||||
"scale": 100.0,
|
||||
"clip": 2.0,
|
||||
},
|
||||
clip=(0.0, 2.0),
|
||||
scale=1.0,
|
||||
)
|
||||
self.observations.policy.support_contact_force = ObsTerm(
|
||||
func=mdp.contact_force_norm,
|
||||
params={
|
||||
"sensor_cfg": SceneEntityCfg("contact_forces", body_names=self.support_link_names),
|
||||
"scale": 100.0,
|
||||
"clip": 2.0,
|
||||
},
|
||||
clip=(0.0, 2.0),
|
||||
scale=1.0,
|
||||
)
|
||||
self.observations.policy.wheel_slip = ObsTerm(
|
||||
func=mdp.wheel_slip_proxy,
|
||||
params={
|
||||
"asset_cfg": SceneEntityCfg("robot", joint_names=self.wheel_joint_names, preserve_order=True),
|
||||
"wheel_radius": 0.096,
|
||||
},
|
||||
clip=(0.0, 3.0),
|
||||
scale=0.5,
|
||||
)
|
||||
self.observations.policy.applied_torque = ObsTerm(
|
||||
func=mdp.applied_torque,
|
||||
params={
|
||||
"asset_cfg": SceneEntityCfg("robot", joint_names=self.joint_names, preserve_order=True),
|
||||
"scale": 80.0,
|
||||
"clip": 2.0,
|
||||
},
|
||||
clip=(-2.0, 2.0),
|
||||
scale=1.0,
|
||||
)
|
||||
|
||||
# ------------------------------Actions------------------------------
|
||||
# reduce action scale
|
||||
@@ -328,8 +283,9 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
|
||||
# Root penalties
|
||||
self.rewards.lin_vel_z_l2.weight = -2.0
|
||||
self.rewards.ang_vel_xy_l2.weight = -0.02
|
||||
self.rewards.flat_orientation_l2.weight = -0.5
|
||||
self.rewards.ang_vel_xy_l2.weight = -0.05
|
||||
self.rewards.flat_orientation_l2.weight = -0.35
|
||||
self.rewards.roll_l2.weight = -0.45
|
||||
self.rewards.base_height_l2.weight = -0.5
|
||||
self.rewards.base_height_l2.params["target_height"] = 0.45
|
||||
self.rewards.base_height_l2.params["asset_cfg"].body_names = [self.base_link_name]
|
||||
@@ -345,7 +301,7 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
self.rewards.joint_vel_l2.params["asset_cfg"].joint_names = self.leg_joint_names
|
||||
self.rewards.joint_vel_wheel_l2.weight = 0
|
||||
self.rewards.joint_vel_wheel_l2.params["asset_cfg"].joint_names = self.wheel_joint_names
|
||||
self.rewards.joint_acc_l2.weight = -2e-7
|
||||
self.rewards.joint_acc_l2.weight = -3e-6
|
||||
self.rewards.joint_acc_l2.params["asset_cfg"].joint_names = self.leg_joint_names
|
||||
self.rewards.joint_acc_wheel_l2.weight = -1e-7
|
||||
self.rewards.joint_acc_wheel_l2.params["asset_cfg"].joint_names = self.wheel_joint_names
|
||||
@@ -361,17 +317,28 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
|
||||
self.rewards.knee_joint_pos_penalty.weight = -0.1
|
||||
self.rewards.knee_joint_pos_penalty.params["asset_cfg"].joint_names = self.knee_joint_names
|
||||
# Inward fold limit: with the knee link vertical at q=0, front knees fold inward for
|
||||
# q < 0 and hind knees for q > 0. Do not allow the legs to pass the vertical
|
||||
# (straight-down) pose on the inward side.
|
||||
self.rewards.knee_inward_fold_penalty.weight = -1.0
|
||||
self.rewards.knee_inward_fold_penalty.params["front_asset_cfg"] = SceneEntityCfg(
|
||||
"robot", joint_names=self.front_knee_joint_names
|
||||
)
|
||||
self.rewards.knee_inward_fold_penalty.params["hind_asset_cfg"] = SceneEntityCfg(
|
||||
"robot", joint_names=self.hind_knee_joint_names
|
||||
)
|
||||
self.rewards.knee_inward_fold_penalty.params["max_inward_tilt"] = 0.0
|
||||
self.rewards.wheel_vel_penalty.weight = 0
|
||||
self.rewards.wheel_vel_penalty.params["sensor_cfg"].body_names = self.foot_link_name
|
||||
self.rewards.wheel_vel_penalty.params["asset_cfg"].joint_names = self.wheel_joint_names
|
||||
self.rewards.joint_mirror.weight = -0.03
|
||||
self.rewards.joint_mirror.weight = 0.0
|
||||
self.rewards.joint_mirror.params["mirror_joints"] = [
|
||||
["fl_knee_joint", "hr_knee_joint"],
|
||||
["fr_knee_joint", "hl_knee_joint"],
|
||||
]
|
||||
|
||||
# Action penalties
|
||||
self.rewards.action_rate_l2.weight = -0.01
|
||||
self.rewards.action_rate_l2.weight = -0.015
|
||||
|
||||
# Contact sensor
|
||||
self.rewards.undesired_contacts.weight = -1.0
|
||||
@@ -424,7 +391,10 @@ class DeeproboticsM20RoughEnvCfg(LocomotionVelocityRoughEnvCfg):
|
||||
# ------------------------------Terminations------------------------------
|
||||
# self.terminations.illegal_contact.params["sensor_cfg"].body_names = [self.base_link_name]
|
||||
self.terminations.illegal_contact = None
|
||||
self.terminations.bad_orientation_2 = None
|
||||
self.terminations.bad_orientation_2 = DoneTerm(
|
||||
func=mdp.bad_orientation,
|
||||
params={"limit_angle": 0.9, "asset_cfg": SceneEntityCfg("robot")},
|
||||
)
|
||||
|
||||
# ------------------------------Curriculums------------------------------
|
||||
# self.curriculum.command_levels.params["range_multiplier"] = (0.2, 1.0)
|
||||
@@ -469,7 +439,7 @@ class DeeproboticsM20SupportRoughEnvCfg(DeeproboticsM20RoughEnvCfg):
|
||||
self.rewards.undesired_contacts.params["sensor_cfg"].body_names = [f"^(?!.*{self.foot_link_name}).*"]
|
||||
self.rewards.undesired_contacts.params["threshold"] = 35.0
|
||||
self.rewards.command_progress.weight = 0.8
|
||||
self.rewards.support_contact_progress.weight = 1.0
|
||||
self.rewards.support_contact_progress.weight = 0.0
|
||||
self.rewards.support_contact_progress.params["sensor_cfg"].body_names = self.support_link_names
|
||||
self.rewards.support_contact_progress.params["wheel_sensor_cfg"].body_names = [self.foot_link_name]
|
||||
self.rewards.support_contact_progress.params["wheel_asset_cfg"].joint_names = self.wheel_joint_names
|
||||
@@ -481,7 +451,8 @@ class DeeproboticsM20SupportRoughEnvCfg(DeeproboticsM20RoughEnvCfg):
|
||||
# Stage 2 should still avoid the bad solution of crawling on the body.
|
||||
self.rewards.track_lin_vel_xy_exp.weight = 1.5
|
||||
self.rewards.track_ang_vel_z_exp.weight = 1.0
|
||||
self.rewards.flat_orientation_l2.weight = -0.45
|
||||
self.rewards.flat_orientation_l2.weight = -0.35
|
||||
self.rewards.roll_l2.weight = -0.45
|
||||
self.rewards.base_height_l2.weight = -0.35
|
||||
self.rewards.base_height_l2.params["target_height"] = 0.29
|
||||
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2025 Deep Robotics
|
||||
# SPDX-License-Identifier: BSD 3-Clause
|
||||
|
||||
"""Smooth random-undulation task: train both pitch and roll to stay at zero."""
|
||||
|
||||
from isaaclab.terrains import TerrainGeneratorCfg
|
||||
from isaaclab.utils import configclass
|
||||
|
||||
import rl_training.tasks.manager_based.locomotion.velocity.mdp as mdp
|
||||
|
||||
from .rough_env_cfg import DeeproboticsM20RoughEnvCfg
|
||||
|
||||
|
||||
@configclass
|
||||
class DeeproboticsM20SmoothEnvCfg(DeeproboticsM20RoughEnvCfg):
|
||||
"""Stage 1 policy on a smooth, randomly undulating terrain, forward-only command."""
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
|
||||
# ------------------------------Terrain------------------------------
|
||||
# Low-frequency sinusoids only: no steps, no sharp edges.
|
||||
self.scene.terrain.terrain_generator = TerrainGeneratorCfg(
|
||||
size=(8.0, 8.0),
|
||||
border_width=2.0,
|
||||
num_rows=10,
|
||||
num_cols=20,
|
||||
horizontal_scale=0.05,
|
||||
vertical_scale=0.005,
|
||||
slope_threshold=None,
|
||||
use_cache=False,
|
||||
curriculum=True,
|
||||
sub_terrains={
|
||||
"smooth_random": mdp.SmoothRandomTerrainCfg(
|
||||
proportion=1.0,
|
||||
border_width=0.25,
|
||||
height_range=(0.10, 0.20),
|
||||
wavelength_range=(1.0, 2.2),
|
||||
num_waves=3,
|
||||
)
|
||||
},
|
||||
)
|
||||
self.scene.terrain.max_init_terrain_level = 9
|
||||
|
||||
# ------------------------------Spawn------------------------------
|
||||
self.events.randomize_reset_base.params = {
|
||||
"pose_range": {
|
||||
"x": (-2.0, 2.0),
|
||||
"y": (-2.0, 2.0),
|
||||
"z": (0.30, 0.30),
|
||||
"roll": (-0.05, 0.05),
|
||||
"pitch": (-0.05, 0.05),
|
||||
"yaw": (-3.14, 3.14),
|
||||
},
|
||||
"velocity_range": {
|
||||
"x": (-0.1, 0.1),
|
||||
"y": (0.0, 0.0),
|
||||
"z": (0.0, 0.0),
|
||||
"roll": (0.0, 0.0),
|
||||
"pitch": (0.0, 0.0),
|
||||
"yaw": (0.0, 0.0),
|
||||
},
|
||||
}
|
||||
|
||||
# Commands keep the original Stage-1 distribution inherited from
|
||||
# DeeproboticsM20RoughEnvCfg: x in [-2, 2] m/s, y = 0, yaw in [-1.5, 1.5]
|
||||
# rad/s, heading command enabled, 2% standing and 100% heading envs.
|
||||
|
||||
# ------------------------------Rewards------------------------------
|
||||
# Split the flat-orientation penalty into true Euler pitch and roll in
|
||||
# radians. Giving -0.4 to each keeps the total strength of the previous
|
||||
# -0.8 flat_orientation_l2 term while letting the two axes be tuned
|
||||
# independently.
|
||||
self.rewards.flat_orientation_l2.weight = 0.0
|
||||
self.rewards.pitch_l2.weight = -0.4
|
||||
self.rewards.roll_l2.weight = -0.4
|
||||
self.rewards.ang_vel_xy_l2.weight = -0.1
|
||||
|
||||
self.disable_zero_weight_rewards()
|
||||
@@ -19,3 +19,4 @@ from .curriculums import * # noqa: F401, F403
|
||||
from .events import * # noqa: F401, F403
|
||||
from .observations import * # noqa: F401, F403
|
||||
from .rewards import * # noqa: F401, F403
|
||||
from .terrain import * # noqa: F401, F403
|
||||
|
||||
@@ -330,6 +330,33 @@ def joint_pos_penalty(
|
||||
return reward
|
||||
|
||||
|
||||
def knee_inward_fold_penalty(
|
||||
env: ManagerBasedRLEnv,
|
||||
front_asset_cfg: SceneEntityCfg,
|
||||
hind_asset_cfg: SceneEntityCfg,
|
||||
max_inward_tilt: float = 0.0,
|
||||
) -> torch.Tensor:
|
||||
"""Penalize front/hind knee joints that fold inward past ``max_inward_tilt``.
|
||||
|
||||
The knee link points straight down when its joint angle is zero, so the signed joint
|
||||
position is the leg's tilt from vertical (all four knee axes are ``-Y`` in the URDF).
|
||||
Front legs fold inward when the angle decreases, hind legs fold inward when it
|
||||
increases. ``max_inward_tilt`` is the largest inward tilt allowed from the vertical
|
||||
(straight-down) pose: 0.0 stops the leg at vertical, ``pi / 2`` would allow it to
|
||||
reach horizontal. Only the inward direction is limited; outward folding is left to
|
||||
the regular joint position limits. The penalty is quadratic in the excess angle and
|
||||
is zero while the joint stays inside the limit.
|
||||
"""
|
||||
front_asset: Articulation = env.scene[front_asset_cfg.name]
|
||||
hind_asset: Articulation = env.scene[hind_asset_cfg.name]
|
||||
front_pos = front_asset.data.joint_pos[:, front_asset_cfg.joint_ids]
|
||||
hind_pos = hind_asset.data.joint_pos[:, hind_asset_cfg.joint_ids]
|
||||
# front: q >= -max_inward_tilt, hind: q <= max_inward_tilt
|
||||
front_excess = torch.clamp(-max_inward_tilt - front_pos, min=0.0)
|
||||
hind_excess = torch.clamp(hind_pos - max_inward_tilt, min=0.0)
|
||||
return torch.sum(front_excess.square() + hind_excess.square(), dim=1)
|
||||
|
||||
|
||||
def wheel_vel_penalty(
|
||||
env: ManagerBasedRLEnv,
|
||||
sensor_cfg: SceneEntityCfg,
|
||||
@@ -944,6 +971,21 @@ def flat_orientation_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = Scen
|
||||
# reward *= torch.clamp(-env.scene["robot"].data.projected_gravity_b[:, 2], 0, 0.7) / 0.7
|
||||
return reward
|
||||
|
||||
|
||||
def roll_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
|
||||
"""Penalize body roll angle in radians (squared)."""
|
||||
asset: RigidObject = env.scene[asset_cfg.name]
|
||||
roll, _, _ = math_utils.euler_xyz_from_quat(asset.data.root_quat_w)
|
||||
return torch.square(roll)
|
||||
|
||||
|
||||
def pitch_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
|
||||
"""Penalize body pitch angle in radians (squared)."""
|
||||
asset: RigidObject = env.scene[asset_cfg.name]
|
||||
_, pitch, _ = math_utils.euler_xyz_from_quat(asset.data.root_quat_w)
|
||||
return torch.square(pitch)
|
||||
|
||||
|
||||
def feet_air_time_including_ang_z(
|
||||
env: ManagerBasedRLEnv, command_name: str, sensor_cfg: SceneEntityCfg, threshold: float
|
||||
) -> torch.Tensor:
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
"""Custom terrains for the FireAnt locomotion tasks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import trimesh
|
||||
|
||||
from isaaclab.terrains import HfTerrainBaseCfg, SubTerrainBaseCfg
|
||||
from isaaclab.terrains.height_field.utils import height_field_to_mesh
|
||||
from isaaclab.utils import configclass
|
||||
|
||||
|
||||
def single_side_bridge_terrain(
|
||||
difficulty: float, cfg: "SingleSideBridgeTerrainCfg"
|
||||
) -> tuple[list[trimesh.Trimesh], np.ndarray]:
|
||||
"""Long raised bridge on the +y half of the cell; flat ground on the -y half.
|
||||
|
||||
The bridge edge is exactly at the cell center line (local ``y = size[1] / 2``),
|
||||
so a robot spawned at the cell center with zero yaw has its left wheels on the
|
||||
bridge and its right wheels on the flat ground.
|
||||
"""
|
||||
height = cfg.bridge_height_range[0] + difficulty * (
|
||||
cfg.bridge_height_range[1] - cfg.bridge_height_range[0]
|
||||
)
|
||||
|
||||
# Ground slab: top surface at z=0, extends over the whole cell.
|
||||
ground = trimesh.creation.box(
|
||||
extents=(cfg.size[0], cfg.size[1], 0.05),
|
||||
transform=trimesh.transformations.translation_matrix(
|
||||
(cfg.size[0] / 2.0, cfg.size[1] / 2.0, -0.025)
|
||||
),
|
||||
)
|
||||
|
||||
# Bridge: full cell length along x, raised strip on the +y side.
|
||||
bridge = trimesh.creation.box(
|
||||
extents=(cfg.size[0], cfg.bridge_width, height),
|
||||
transform=trimesh.transformations.translation_matrix(
|
||||
(
|
||||
cfg.size[0] / 2.0,
|
||||
cfg.size[1] / 2.0 + cfg.bridge_width / 2.0,
|
||||
height / 2.0,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
origin = np.array((cfg.size[0] / 2.0, cfg.size[1] / 2.0, 0.0))
|
||||
return [ground, bridge], origin
|
||||
|
||||
|
||||
@configclass
|
||||
class SingleSideBridgeTerrainCfg(SubTerrainBaseCfg):
|
||||
"""Configuration for a long single-side bridge terrain."""
|
||||
|
||||
function = single_side_bridge_terrain
|
||||
|
||||
bridge_height_range: tuple[float, float] = (0.06, 0.14)
|
||||
"""Bridge height at the start and end of the difficulty curriculum (m)."""
|
||||
|
||||
bridge_width: float = 1.5
|
||||
"""Width of the raised bridge on the +y side of the cell (m)."""
|
||||
|
||||
|
||||
@height_field_to_mesh
|
||||
def smooth_random_terrain(difficulty: float, cfg: "SmoothRandomTerrainCfg") -> np.ndarray:
|
||||
"""Smooth low-frequency random height field.
|
||||
|
||||
The surface is a sum of a few low-frequency sinusoids. It has no steps or
|
||||
sharp edges, so the robot has to keep its body level over gentle, random
|
||||
pitch and roll disturbances.
|
||||
"""
|
||||
width_pixels = int(cfg.size[0] / cfg.horizontal_scale)
|
||||
length_pixels = int(cfg.size[1] / cfg.horizontal_scale)
|
||||
x = np.arange(width_pixels, dtype=np.float64) * cfg.horizontal_scale
|
||||
y = np.arange(length_pixels, dtype=np.float64) * cfg.horizontal_scale
|
||||
grid_x, grid_y = np.meshgrid(x, y, indexing="ij")
|
||||
|
||||
amplitude = cfg.height_range[0] + difficulty * (cfg.height_range[1] - cfg.height_range[0])
|
||||
heights = np.zeros_like(grid_x)
|
||||
for _ in range(cfg.num_waves):
|
||||
direction = np.random.uniform(0.0, 2.0 * np.pi)
|
||||
wavelength = np.random.uniform(*cfg.wavelength_range)
|
||||
phase = np.random.uniform(0.0, 2.0 * np.pi)
|
||||
weight = np.random.uniform(0.5, 1.0)
|
||||
projection = grid_x * np.cos(direction) + grid_y * np.sin(direction)
|
||||
heights += weight * np.sin(2.0 * np.pi * projection / wavelength + phase)
|
||||
|
||||
heights -= heights.mean()
|
||||
peak = float(np.max(np.abs(heights)))
|
||||
if peak > 1e-9:
|
||||
heights *= amplitude / peak
|
||||
return np.rint(heights / cfg.vertical_scale).astype(np.int16)
|
||||
|
||||
|
||||
@configclass
|
||||
class SmoothRandomTerrainCfg(HfTerrainBaseCfg):
|
||||
"""Configuration for a smooth random-undulation terrain."""
|
||||
|
||||
function = smooth_random_terrain
|
||||
|
||||
height_range: tuple[float, float] = (0.02, 0.05)
|
||||
"""Peak height of the undulations at the start and end of the curriculum (m)."""
|
||||
|
||||
wavelength_range: tuple[float, float] = (1.5, 3.0)
|
||||
"""Spatial wavelength range of the undulations (m)."""
|
||||
|
||||
num_waves: int = 3
|
||||
"""Number of sinusoids summed to form the surface."""
|
||||
+2
@@ -386,6 +386,8 @@ class RewardsCfg:
|
||||
lin_vel_z_l2 = RewTerm(func=mdp.lin_vel_z_l2, weight=0.0)
|
||||
ang_vel_xy_l2 = RewTerm(func=mdp.ang_vel_xy_l2, weight=0.0)
|
||||
flat_orientation_l2 = RewTerm(func=mdp.flat_orientation_l2, weight=0.0)
|
||||
roll_l2 = RewTerm(func=mdp.roll_l2, weight=0.0)
|
||||
pitch_l2 = RewTerm(func=mdp.pitch_l2, weight=0.0)
|
||||
base_height_l2 = RewTerm(
|
||||
func=mdp.base_height_l2,
|
||||
weight=0.0,
|
||||
|
||||
Reference in New Issue
Block a user