Skip to content

Commit 659f210

Browse files
authored
Fix reward weights and update README (#4)
1 parent 5b6b35c commit 659f210

8 files changed

Lines changed: 178 additions & 124 deletions

File tree

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
This repository contains the official implementation for the paper "Versatile Loco-Manipulation through Flexible Interlimb Coordination", accepted to CoRL 2025.
1212

13-
[**Project Website**](https://relic-locoman.rai-inst.com/) | [**arXiv Paper**](https://arxiv.org/abs/2506.07876)
13+
[**Project Website**](https://relic-locoman.rai-inst.com/) | [**arXiv Paper**](https://arxiv.org/abs/2506.07876) | [**Blog Post**](https://rai-inst.com/resources/blog/reinforcement-learning-for-flexible-loco-manipulation/) | [**X**](https://x.com/rai_inst/status/1971590845817045218) | [**Threads**](https://www.threads.com/@robotics_and_ai_institute/post/DPEdj0UFEnm?xmt=AQF0fkVTtJFB-UTiEjOQATobby44KSl_DdfDymPMpr42Vg)
1414

1515
---
1616
*Our ReLIC policy enables a quadruped robot to walk with three legs and manipulate with the arm and one leg.*
@@ -44,4 +44,22 @@ This project uses [Pixi](https://pixi.sh/latest/installation/) to manage depende
4444

4545
Alternatively, you can install the project without Pixi by following the standard installation guides for [IsaacLab](https://isaac-sim.github.io/IsaacLab/v2.1.0/source/setup/installation/isaaclab_pip_installation.html) and its [extensions](https://github.com/isaac-sim/IsaacLabExtensionTemplate/tree/main?tab=readme-ov-file#installation).
4646

47-
## Training
47+
## Training and Eval
48+
49+
```bash
50+
python scripts/rsl_rl/train.py --task Isaac-Spot-Interlimb-Phase-1-v0 --headless
51+
python scripts/rsl_rl/play.py --task Isaac-Spot-Interlimb-Play-v0 --center
52+
```
53+
54+
To achieve optimal deployment results, we implemented a weight curriculum with multiple training phases. Users can fine-tune the models from `Phase-2` to `Phase-4` to reproduce the results presented in our paper. The pre-trained weights can be found in `relic/source/relic/relic/assets/spot/pretrained`.
55+
56+
## Deployment
57+
58+
Please find deployment code with Boston Dynamics's Spot robot at our deployment branch.
59+
60+
## Adapted Code
61+
62+
We use RSL_RL for RL training and adapt the following scripts from [IsaacLabExtensionTemplate](https://github.com/isaac-sim/IsaacLabExtensionTemplate/tree/main?tab=readme-ov-file#installation)
63+
- `scripts/rsl_rl/*`
64+
- `source/relic/pyproject.toml`
65+
- `source/relic/setup.py`

scripts/rsl_rl/play.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"--num_envs", type=int, default=None, help="Number of environments to simulate."
3636
)
3737
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
38+
parser.add_argument(
39+
"--center", action="store_true", default=False, help="Look at the robot."
40+
)
3841
# append RSL-RL cli arguments
3942
cli_args.add_rsl_rl_args(parser)
4043
# append AppLauncher cli args
@@ -83,6 +86,13 @@ def main():
8386
args_cli.task, args_cli
8487
)
8588

89+
if args_cli.center:
90+
env_cfg.viewer.origin_type = "asset_root"
91+
env_cfg.viewer.asset_name = "robot"
92+
env_cfg.viewer.env_index = 10
93+
env_cfg.viewer.eye = (3.0, 3.0, 3.0)
94+
env_cfg.viewer.resolution = (4096, 2160)
95+
8696
# specify directory for logging experiments
8797
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
8898
log_root_path = os.path.abspath(log_root_path)
@@ -125,19 +135,30 @@ def main():
125135
# obtain the trained policy for inference
126136
policy = ppo_runner.get_inference_policy(device=env.unwrapped.device)
127137

138+
# extract the neural network module
139+
# we do this in a try-except to maintain backwards compatibility.
140+
try:
141+
# version 2.3 onwards
142+
policy_nn = ppo_runner.alg.policy
143+
except AttributeError:
144+
# version 2.2 and below
145+
policy_nn = ppo_runner.alg.actor_critic
146+
147+
# extract the normalizer
148+
if hasattr(policy_nn, "actor_obs_normalizer"):
149+
normalizer = policy_nn.actor_obs_normalizer
150+
elif hasattr(policy_nn, "student_obs_normalizer"):
151+
normalizer = policy_nn.student_obs_normalizer
152+
else:
153+
normalizer = None
154+
128155
# export policy to onnx/jit
129156
export_model_dir = os.path.join(os.path.dirname(resume_path), "exported")
130157
export_policy_as_jit(
131-
ppo_runner.alg.actor_critic,
132-
ppo_runner.obs_normalizer,
133-
path=export_model_dir,
134-
filename="policy.pt",
158+
policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.pt"
135159
)
136160
export_policy_as_onnx(
137-
ppo_runner.alg.actor_critic,
138-
normalizer=ppo_runner.obs_normalizer,
139-
path=export_model_dir,
140-
filename="policy.onnx",
161+
policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.onnx"
141162
)
142163

143164
# reset environment
819 KB
Binary file not shown.
829 KB
Binary file not shown.

source/relic/relic/assets/spot/spot.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,17 @@
6565
effort_limit=HIP_EFFORT_LIMIT,
6666
stiffness=HIP_STIFFNESS,
6767
damping=HIP_DAMPING,
68-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
69-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
68+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
69+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
7070
),
7171
"spot_knee": SpotKneeActuatorCfg(
7272
joint_names_expr=[".*_kn"],
7373
joint_parameter_lookup=JOINT_PARAMETER_LOOKUP_TABLE,
7474
effort_limit=None, # torque limits are handled based experimental data
7575
stiffness=KNEE_STIFFNESS,
7676
damping=KNEE_DAMPING,
77-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
78-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
77+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
78+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
7979
enable_torque_speed_limit=True,
8080
),
8181
"spot_arm_sh0": DelayedPDActuatorCfg(
@@ -84,62 +84,62 @@
8484
stiffness=ARM_STIFFNESS[0],
8585
damping=ARM_DAMPING[0],
8686
armature=ARM_ARMATURE[0],
87-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
88-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
87+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
88+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
8989
),
9090
"spot_arm_sh1": DelayedPDActuatorCfg(
9191
joint_names_expr=["arm_sh1"],
9292
effort_limit=ARM_EFFORT_LIMIT[1],
9393
stiffness=ARM_STIFFNESS[1],
9494
damping=ARM_DAMPING[1],
9595
armature=ARM_ARMATURE[1],
96-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
97-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
96+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
97+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
9898
),
9999
"spot_arm_el0": DelayedPDActuatorCfg(
100100
joint_names_expr=["arm_el0"],
101101
effort_limit=ARM_EFFORT_LIMIT[2],
102102
stiffness=ARM_STIFFNESS[2],
103103
damping=ARM_DAMPING[2],
104104
armature=ARM_ARMATURE[2],
105-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
106-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
105+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
106+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
107107
),
108108
"spot_arm_el1": DelayedPDActuatorCfg(
109109
joint_names_expr=["arm_el1"],
110110
effort_limit=ARM_EFFORT_LIMIT[3],
111111
stiffness=ARM_STIFFNESS[3],
112112
damping=ARM_DAMPING[3],
113113
armature=ARM_ARMATURE[3],
114-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
115-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
114+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
115+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
116116
),
117117
"spot_arm_wr0": DelayedPDActuatorCfg(
118118
joint_names_expr=["arm_wr0"],
119119
effort_limit=ARM_EFFORT_LIMIT[4],
120120
stiffness=ARM_STIFFNESS[4],
121121
damping=ARM_DAMPING[4],
122122
armature=ARM_ARMATURE[4],
123-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
124-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
123+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
124+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
125125
),
126126
"spot_arm_wr1": DelayedPDActuatorCfg(
127127
joint_names_expr=["arm_wr1"],
128128
effort_limit=ARM_EFFORT_LIMIT[5],
129129
stiffness=ARM_STIFFNESS[5],
130130
damping=ARM_DAMPING[5],
131131
armature=ARM_ARMATURE[5],
132-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
133-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
132+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
133+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
134134
),
135135
"spot_arm_f1x": DelayedPDActuatorCfg(
136136
joint_names_expr=["arm_f1x"],
137137
effort_limit=ARM_EFFORT_LIMIT[6],
138138
stiffness=ARM_STIFFNESS[6],
139139
damping=ARM_DAMPING[6],
140140
armature=ARM_ARMATURE[6],
141-
min_delay=1, # physics time steps (min: 5.0*1=5.0ms)
142-
max_delay=2, # physics time steps (max: 5.0*2=10.0ms)
141+
min_delay=0, # physics time steps (min: 5.0*1=5.0ms)
142+
max_delay=3, # physics time steps (max: 5.0*2=10.0ms)
143143
),
144144
},
145145
)

source/relic/relic/tasks/loco_manipulation/config/spot/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
},
3939
)
4040

41+
gym.register(
42+
id="Isaac-Spot-Interlimb-Phase-4-v0",
43+
entry_point="isaaclab.envs:ManagerBasedRLEnv",
44+
disable_env_checker=True,
45+
kwargs={
46+
"env_cfg_entry_point": spot_env_cfg.SpotInterlimbEnvCfg_Phase_4,
47+
"rsl_rl_cfg_entry_point": agents.rsl_rl_cfg.SpotInterlimbPPORunnerCfg,
48+
},
49+
)
50+
4151
gym.register(
4252
id="Isaac-Spot-Interlimb-Play-v0",
4353
entry_point="isaaclab.envs:ManagerBasedRLEnv",

source/relic/relic/tasks/loco_manipulation/config/spot/spot_env_cfg.py

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,22 @@ def __post_init__(self):
1919
# switch robot to spot-arm
2020
self.scene.robot = SPOT_ARM_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
2121
self.scene.robot.spawn.joint_drive.gains.stiffness = None
22-
# self.rewards.foot_clearance = None
22+
23+
# Weights
24+
self.rewards.track_lin_vel_xy_exp.weight = 7.0
25+
self.rewards.track_ang_vel_z_exp.weight = 3.5
26+
self.rewards.track_base_orientation_l2.weight = -7.0
27+
self.rewards.track_base_height_l2.weight = -3.5
28+
self.rewards.dof_torques_l2.weight = -1.0e-05
29+
self.rewards.dof_acc_l2.weight = -2.5e-7
30+
self.rewards.action_rate_l2.weight = -0.01
31+
self.rewards.feet_air_time.weight = 0.01
32+
self.rewards.joint_energy.weight = 2.0
33+
self.rewards.air_time_variance.weight = -10.0
34+
self.rewards.flight_penalty.weight = -5.0
35+
self.rewards.foot_impact.weight = -1.0
36+
self.rewards.foot_slip.weight = -0.5
37+
self.rewards.foot_clearance.weight = 0.0
2338

2439

2540
@configclass
@@ -31,25 +46,29 @@ def __post_init__(self):
3146
super().__post_init__()
3247
# switch robot to spot-arm
3348
self.scene.robot = SPOT_ARM_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
49+
self.scene.robot.spawn.joint_drive.gains.stiffness = None
3450

35-
# change event and termination terms
36-
self.events.physics_material.params["static_friction_range"] = (0.8, 0.8)
37-
self.events.physics_material.params["dynamic_friction_range"] = (0.6, 0.6)
51+
# relax contact events
3852
self.terminations.undesired_ground_contact.params["sensor_cfg"].body_names = [
3953
".*uleg"
4054
]
4155

42-
# change the reward weights
43-
self.rewards.dof_torques_l2.weight = (-1.0e-4,) # -1.0e-05
44-
self.rewards.dof_acc_l2.weight = (-1.0e-6,) # -2.5e-7
45-
self.rewards.action_rate_l2.weight = -0.1 # -0.01
56+
# Weights
57+
self.rewards.track_lin_vel_xy_exp.weight = 7.0
58+
self.rewards.track_ang_vel_z_exp.weight = 3.5
4659
self.rewards.track_base_orientation_l2.weight = -7.0
4760
self.rewards.track_base_height_l2.weight = -30.0
61+
self.rewards.dof_torques_l2.weight = -0.0001
62+
self.rewards.dof_acc_l2.weight = -1.0e-06
63+
self.rewards.action_rate_l2.weight = -0.1
64+
self.rewards.feet_air_time.weight = 0.01
65+
self.rewards.joint_energy.weight = 2.0
66+
self.rewards.air_time_variance.weight = -10.0
4867
self.rewards.flight_penalty.weight = -10.0
68+
self.rewards.foot_impact.weight = -1.0
69+
self.rewards.foot_slip.weight = -0.5
4970
self.rewards.foot_clearance.weight = 1.0
5071

51-
self.rewards.foot_slip.weight = -1.0
52-
5372

5473
@configclass
5574
class SpotInterlimbEnvCfg_Phase_3(InterlimbEnvCfg):
@@ -60,31 +79,65 @@ def __post_init__(self):
6079
super().__post_init__()
6180
# switch robot to spot-arm
6281
self.scene.robot = SPOT_ARM_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
82+
self.scene.robot.spawn.joint_drive.gains.stiffness = None
6383

6484
# change event and termination terms
65-
self.events.physics_material.params["static_friction_range"] = (0.3, 1.0)
66-
self.events.physics_material.params["dynamic_friction_range"] = (0.3, 0.8)
6785
self.terminations.undesired_ground_contact.params["sensor_cfg"].body_names = [
6886
".*uleg"
6987
]
7088

71-
# change the reward weights
72-
# self.rewards.dof_torques_l2.weight = -5.0e-4 # -1.0e-4, -1.0e-05
73-
# self.rewards.dof_acc_l2.weight = -2.0e-6 # -1.0e-6, -2.5e-7
74-
self.rewards.dof_torques_l2.params["leg_ctl_weight"] = -2.0e-4
75-
self.rewards.dof_acc_l2.params["leg_ctl_weight"] = -1.0e-6
76-
77-
self.rewards.action_rate_l2.weight = -0.1
89+
# Weights
90+
self.rewards.track_lin_vel_xy_exp.weight = 7.0
91+
self.rewards.track_ang_vel_z_exp.weight = 3.5
7892
self.rewards.track_base_orientation_l2.weight = -45.0
7993
self.rewards.track_base_height_l2.weight = -120.0
94+
self.rewards.dof_torques_l2.weight = -0.0001
95+
self.rewards.dof_acc_l2.weight = -1.0e-06
96+
self.rewards.action_rate_l2.weight = -0.1
97+
self.rewards.feet_air_time.weight = 0.01
98+
self.rewards.joint_energy.weight = 2.0
99+
self.rewards.air_time_variance.weight = -10.0
80100
self.rewards.flight_penalty.weight = -10.0
101+
self.rewards.foot_impact.weight = -1.0
102+
self.rewards.foot_slip.weight = -1.0
81103
self.rewards.foot_clearance.weight = 1.0
82-
self.rewards.foot_slip.weight = -2.0
83104

84-
self.rewards.feet_air_time_target.weight = 0.5
105+
106+
@configclass
107+
class SpotInterlimbEnvCfg_Phase_4(InterlimbEnvCfg):
108+
"""Configuration for the phase 4 environment."""
109+
110+
def __post_init__(self):
111+
# post init of parent
112+
super().__post_init__()
113+
# switch robot to spot-arm
114+
self.scene.robot = SPOT_ARM_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
115+
self.scene.robot.spawn.joint_drive.gains.stiffness = None
116+
117+
# change event and termination terms
118+
self.terminations.undesired_ground_contact.params["sensor_cfg"].body_names = [
119+
".*uleg"
120+
]
121+
122+
# Weights
123+
self.rewards.track_lin_vel_xy_exp.weight = 7.0
124+
self.rewards.track_ang_vel_z_exp.weight = 3.5
125+
self.rewards.track_base_orientation_l2.weight = -45.0
126+
self.rewards.track_base_height_l2.weight = -120.0
127+
self.rewards.dof_torques_l2.weight = -0.0001
128+
self.rewards.dof_acc_l2.weight = -1.0e-06
129+
self.rewards.action_rate_l2.weight = -0.1
130+
self.rewards.feet_air_time.weight = 0.01
131+
self.rewards.joint_energy.weight = 2.0
85132
self.rewards.air_time_variance.weight = 0.0
86-
self.rewards.lin_vel_z_l2.weight = -0.0
87-
self.rewards.ang_vel_xy_l2.weight = -0.0
133+
self.rewards.flight_penalty.weight = -10.0
134+
self.rewards.foot_impact.weight = -1.0
135+
self.rewards.foot_slip.weight = -2.0
136+
self.rewards.foot_clearance.weight = 1.0
137+
self.rewards.dof_torque_limits_l2.weight = -1.0
138+
self.rewards.gait.weight = 5.0
139+
self.rewards.three_leg_gait.weight = 5.0
140+
self.rewards.feet_air_time_target.weight = 0.5
88141

89142

90143
@configclass

0 commit comments

Comments
 (0)