Skip to content

Commit cacdffb

Browse files
📝 Add docstrings to g1-energy-benchmark-3027240647789121151
Docstrings generation was requested by @ngoiyaeric. * #1 (comment) The following files were modified: * `source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/g1_energy/env.py` * `source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/g1_energy/env_cfg.py` * `source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/g1_energy/mdp/observations.py` * `source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/g1_energy/mdp/rewards.py` * `source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/g1_energy/mdp/terminations.py`
1 parent e173128 commit cacdffb

5 files changed

Lines changed: 347 additions & 0 deletions

File tree

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
import torch
7+
8+
from isaaclab.envs.manager_based_rl_env import ManagerBasedRLEnv
9+
10+
11+
class G1EnergyEnv(ManagerBasedRLEnv):
12+
"""
13+
Custom environment for the G1 robot with an energy benchmark.
14+
This intercepts the environment step to track battery state and tokens.
15+
"""
16+
17+
def __init__(self, cfg, **kwargs):
18+
# 1. Allocate custom buffers before calling super().__init__()
19+
# These will be initialized to 1.0 (full battery) and 0.0 (no tokens)
20+
"""
21+
Initialize the G1EnergyEnv and prepare placeholders for per-environment energy buffers.
22+
23+
Initializes attributes used by the energy benchmark: `battery_buf` and `tokens_buf` are set to None here and will be allocated and initialized inside `load_managers` (buffers are initialized to full battery and zero tokens once the number of environments and device are known). Stores the provided configuration in `_cfg` and then delegates further initialization to the parent class by calling `super().__init__`, which triggers `load_managers`.
24+
25+
Parameters:
26+
cfg: Configuration object or mapping containing environment settings used by this class. Expected keys used later include `battery_capacity`, `battery_drain_rate`, `token_earn_rate`, `charge_token_cost`, and `charging_station_radius`.
27+
**kwargs: Additional keyword arguments forwarded to the parent class initializer.
28+
"""
29+
self.battery_buf = None
30+
self.tokens_buf = None
31+
self._cfg = cfg
32+
33+
# Super init will call load_managers, so we will initialize the buffers inside load_managers
34+
super().__init__(cfg, **kwargs)
35+
36+
def load_managers(self):
37+
"""
38+
Prepare per-environment energy state and load energy-related configuration.
39+
40+
Initializes `battery_buf` to ones and `tokens_buf` to zeros with length `self.num_envs` on `self.device`, and stores energy parameters from `self.cfg`: `max_battery` (battery_capacity), `battery_drain_rate`, `token_earn_rate`, `charge_token_cost`, and `charging_station_radius`.
41+
"""
42+
super().load_managers()
43+
# Initialize the buffers now that the number of environments is known
44+
self.battery_buf = torch.ones(self.num_envs, device=self.device)
45+
self.tokens_buf = torch.zeros(self.num_envs, device=self.device)
46+
47+
# Parameters for energy / tokens
48+
self.max_battery = self.cfg.battery_capacity
49+
self.battery_drain_rate = self.cfg.battery_drain_rate
50+
self.token_earn_rate = self.cfg.token_earn_rate
51+
self.charge_token_cost = self.cfg.charge_token_cost
52+
self.charging_station_radius = self.cfg.charging_station_radius
53+
54+
def step(self, action: torch.Tensor):
55+
# Process actions
56+
"""
57+
Advance the environment one control step using the provided actions, update energy/tokens state, run managers, handle resets, and return the latest observations and metrics.
58+
59+
Parameters:
60+
action (torch.Tensor): Actions for all environments; will be applied to the simulation.
61+
62+
Returns:
63+
tuple: A 5-tuple (obs_buf, reward_buf, reset_terminated, reset_time_outs, extras) where
64+
- obs_buf: latest observation buffer for all environments,
65+
- reward_buf: reward values computed for this step,
66+
- reset_terminated: boolean mask indicating environments reset due to termination,
67+
- reset_time_outs: boolean mask indicating environments reset due to timeout,
68+
- extras: dictionary of additional info and logged metrics.
69+
"""
70+
self.action_manager.process_action(action.to(self.device))
71+
self.recorder_manager.record_pre_step()
72+
73+
is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors()
74+
75+
# Perform physics stepping
76+
for _ in range(self.cfg.decimation):
77+
self._sim_step_counter += 1
78+
self.action_manager.apply_action()
79+
self.scene.write_data_to_sim()
80+
self.sim.step(render=False)
81+
self.recorder_manager.record_post_physics_decimation_step()
82+
83+
if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering:
84+
self.sim.render()
85+
86+
self.scene.update(dt=self.physics_dt)
87+
88+
# -- UPDATE BUFFERS AND ENERGY/TOKENS --
89+
self.episode_length_buf += 1
90+
self.common_step_counter += 1
91+
92+
# Calculate energy drain based on power consumption
93+
robot = self.scene["robot"]
94+
95+
# Using simplified energy drain: sum of absolute torques
96+
# You could also use the actual power formula: |torque * velocity|
97+
energy_drain = torch.sum(torch.abs(robot.data.applied_torque), dim=1) * self.battery_drain_rate * self.step_dt
98+
self.battery_buf = torch.clamp(self.battery_buf - energy_drain, min=0.0, max=self.max_battery)
99+
100+
# Calculate token earning (Job: Tracking velocity)
101+
# Job quality depends on linear velocity tracking error
102+
vel_cmd = self.command_manager.get_command("base_velocity")
103+
current_vel = robot.data.root_lin_vel_b
104+
105+
vel_error = torch.sum(torch.square(vel_cmd[:, :2] - current_vel[:, :2]), dim=1)
106+
107+
# Earn tokens if error is low (robot is doing its job well)
108+
job_quality = torch.exp(-vel_error / 0.5)
109+
tokens_earned = job_quality * self.token_earn_rate * self.step_dt
110+
self.tokens_buf += tokens_earned
111+
112+
# Charging logic
113+
# Check distance to charging station (origin 0,0)
114+
dist_to_station = torch.norm(robot.data.root_pos_w[:, :2], dim=1)
115+
at_station = dist_to_station < self.charging_station_radius
116+
can_charge = self.tokens_buf >= self.charge_token_cost
117+
118+
charging_envs = at_station & can_charge
119+
120+
if charging_envs.any():
121+
charging_ids = charging_envs.nonzero(as_tuple=False).flatten()
122+
123+
# Apply charge
124+
self.battery_buf[charging_ids] = self.max_battery
125+
self.tokens_buf[charging_ids] -= self.charge_token_cost
126+
127+
# Trigger custom event for visual / logging if needed
128+
if "at_charging_station" in self.event_manager.available_modes:
129+
self.event_manager.apply(mode="at_charging_station", env_ids=charging_ids)
130+
131+
# -- MANAGERS --
132+
self.reset_buf = self.termination_manager.compute()
133+
self.reset_terminated = self.termination_manager.terminated
134+
self.reset_time_outs = self.termination_manager.time_outs
135+
136+
self.reward_buf = self.reward_manager.compute(dt=self.step_dt)
137+
138+
if len(self.recorder_manager.active_terms) > 0:
139+
self.obs_buf = self.observation_manager.compute()
140+
self.recorder_manager.record_post_step()
141+
142+
# Reset environments
143+
reset_env_ids = self.reset_buf.nonzero(as_tuple=False).squeeze(-1)
144+
if len(reset_env_ids) > 0:
145+
self.recorder_manager.record_pre_reset(reset_env_ids)
146+
self._reset_idx(reset_env_ids)
147+
148+
if self.sim.has_rtx_sensors() and self.cfg.num_rerenders_on_reset > 0:
149+
for _ in range(self.cfg.num_rerenders_on_reset):
150+
self.sim.render()
151+
152+
self.recorder_manager.record_post_reset(reset_env_ids)
153+
154+
# -- COMMANDS & EVENTS --
155+
self.command_manager.compute(dt=self.step_dt)
156+
if "interval" in self.event_manager.available_modes:
157+
self.event_manager.apply(mode="interval", dt=self.step_dt)
158+
159+
self.obs_buf = self.observation_manager.compute(update_history=True)
160+
161+
return (
162+
self.obs_buf,
163+
self.reward_buf,
164+
self.reset_terminated,
165+
self.reset_time_outs,
166+
self.extras,
167+
)
168+
169+
def _reset_idx(self, env_ids: torch.Tensor):
170+
"""
171+
Reset the specified environments' energy-related state and record average metrics.
172+
173+
Resets battery level to full and tokens to zero for the given environment indices, then stores the average battery and token values in extras["log"] under "Metrics/avg_battery" and "Metrics/avg_tokens".
174+
175+
Parameters:
176+
env_ids (torch.Tensor): 1D tensor of environment indices to reset.
177+
"""
178+
super()._reset_idx(env_ids)
179+
180+
# Reset custom buffers
181+
self.battery_buf[env_ids] = self.max_battery
182+
self.tokens_buf[env_ids] = 0.0
183+
184+
# Add custom metrics logging
185+
avg_battery = torch.mean(self.battery_buf).item()
186+
avg_tokens = torch.mean(self.tokens_buf).item()
187+
188+
self.extras["log"]["Metrics/avg_battery"] = avg_battery
189+
self.extras["log"]["Metrics/avg_tokens"] = avg_tokens
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
from isaaclab.managers import EventTermCfg as EventTerm
7+
from isaaclab.managers import ObservationTermCfg as ObsTerm
8+
from isaaclab.managers import RewardTermCfg as RewTerm
9+
from isaaclab.managers import TerminationTermCfg as DoneTerm
10+
from isaaclab.utils import configclass
11+
12+
from isaaclab_tasks.manager_based.locomotion.velocity.config.g1.flat_env_cfg import (
13+
G1FlatEnvCfg,
14+
)
15+
16+
import source.isaaclab_tasks.isaaclab_tasks.manager_based.locomotion.velocity.config.g1_energy.mdp as custom_mdp
17+
18+
19+
@configclass
20+
class G1EnergyEnvCfg(G1FlatEnvCfg):
21+
"""Configuration for the G1 Energy Environment."""
22+
23+
# Energy / Token configurations
24+
battery_capacity: float = 1.0
25+
battery_drain_rate: float = 0.005 # Rate of battery drain based on torque
26+
token_earn_rate: float = 0.1 # Tokens earned per second of good tracking
27+
charge_token_cost: float = 1.0 # Cost in tokens to fully charge
28+
charging_station_radius: float = 1.0 # Radius to trigger charging at origin
29+
30+
def __post_init__(self):
31+
"""
32+
Finalize post-initialization for the energy environment configuration by applying energy-specific command limits, episode length, terminations, rewards, observations, and event tracking.
33+
34+
This method adjusts the base movement command ranges, extends the episode duration to accommodate charging behavior, registers a battery-empty termination, adds battery-related reward terms (including an empty-battery penalty), exposes battery level and token count as policy observations, and enables tracking of the "at_charging_station" event.
35+
"""
36+
super().__post_init__()
37+
38+
# Overwrite the base environment commands
39+
# Allow the robot to track X, Y and Yaw
40+
self.commands.base_velocity.ranges.lin_vel_x = (0.0, 1.0)
41+
self.commands.base_velocity.ranges.lin_vel_y = (-0.5, 0.5)
42+
self.commands.base_velocity.ranges.ang_vel_z = (-1.0, 1.0)
43+
44+
# Extend episode length to allow for longer charging cycles
45+
self.episode_length_s = 60.0
46+
47+
# Terminations
48+
self.terminations.battery_empty = DoneTerm(func=custom_mdp.battery_empty, time_out=True)
49+
50+
# Rewards
51+
self.rewards.battery_penalty = RewTerm(
52+
func=custom_mdp.battery_penalty, weight=0.1
53+
) # Note: returns negative inside
54+
self.rewards.empty_battery_penalty = RewTerm(func=custom_mdp.empty_battery_penalty, weight=1.0)
55+
56+
# Observations
57+
# Add the custom observation terms to the policy observation space
58+
self.observations.policy.battery_level = ObsTerm(func=custom_mdp.battery_level)
59+
self.observations.policy.token_count = ObsTerm(func=custom_mdp.token_count)
60+
61+
# Ensure event mode is tracked
62+
self.events.at_charging_station = EventTerm(func=lambda env, env_ids: None, mode="at_charging_station")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
import torch
7+
8+
from isaaclab.envs.manager_based_rl_env import ManagerBasedRLEnv
9+
10+
11+
def battery_level(env: ManagerBasedRLEnv) -> torch.Tensor:
12+
"""
13+
Get the robot's battery level normalized to its maximum capacity.
14+
15+
Parameters:
16+
env (ManagerBasedRLEnv): Environment containing `battery_buf` and `max_battery`.
17+
18+
Returns:
19+
torch.Tensor: Tensor of shape (num_envs, 1) with values in [0, 1] representing each environment's battery level divided by `max_battery`.
20+
"""
21+
# (num_envs, 1)
22+
return (env.battery_buf / env.max_battery).unsqueeze(-1)
23+
24+
25+
def token_count(env: ManagerBasedRLEnv) -> torch.Tensor:
26+
"""
27+
Provide the current token count per environment as a single-column tensor.
28+
29+
Returns:
30+
torch.Tensor: Tensor of shape (num_envs, 1) containing the token count for each environment.
31+
"""
32+
# (num_envs, 1)
33+
return env.tokens_buf.unsqueeze(-1)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
import torch
7+
8+
from isaaclab.envs.manager_based_rl_env import ManagerBasedRLEnv
9+
10+
11+
def battery_penalty(env: ManagerBasedRLEnv) -> torch.Tensor:
12+
"""
13+
Compute a linear penalty proportional to remaining battery.
14+
15+
Parameters:
16+
env (ManagerBasedRLEnv): Environment providing `battery_buf` and `max_battery`.
17+
18+
Returns:
19+
torch.Tensor: Penalty computed as -(1.0 - (env.battery_buf / env.max_battery)); values are 0 when battery is full and approach -1 as battery approaches empty.
20+
"""
21+
# Scale from 0 (full) to -1 (empty) linearly
22+
# You could make it non-linear e.g., only penalize if below 20%
23+
return -(1.0 - (env.battery_buf / env.max_battery))
24+
25+
26+
def empty_battery_penalty(env: ManagerBasedRLEnv) -> torch.Tensor:
27+
"""
28+
Apply a heavy penalty when the environment's battery is effectively empty.
29+
30+
Parameters:
31+
env (ManagerBasedRLEnv): Environment exposing `battery_buf` (current battery level),
32+
`device` (tensor device), and `max_battery` (not used here).
33+
34+
Returns:
35+
torch.Tensor: A scalar tensor on `env.device` with value `-10.0` if `env.battery_buf <= 0.01`,
36+
`0.0` otherwise.
37+
"""
38+
return torch.where(
39+
env.battery_buf <= 0.01,
40+
torch.tensor(-10.0, device=env.device),
41+
torch.tensor(0.0, device=env.device),
42+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
import torch
7+
8+
from isaaclab.envs.manager_based_rl_env import ManagerBasedRLEnv
9+
10+
11+
def battery_empty(env: ManagerBasedRLEnv) -> torch.Tensor:
12+
"""
13+
Indicates whether an episode should terminate when the environment's battery level is depleted.
14+
15+
Parameters:
16+
env (ManagerBasedRLEnv): Environment whose `battery_buf` tensor is checked for depletion.
17+
18+
Returns:
19+
torch.Tensor: Boolean tensor with `True` where `battery_buf` is less than or equal to 0.0, `False` otherwise.
20+
"""
21+
return env.battery_buf <= 0.0

0 commit comments

Comments
 (0)