From IsaacGymEnvs#
IsaacGymEnvs was a reinforcement learning framework designed for the Isaac Gym Preview Release. As both IsaacGymEnvs and the Isaac Gym Preview Release are now deprecated, the following guide walks through the key differences between IsaacGymEnvs and Isaac Lab, as well as differences in APIs between Isaac Gym Preview Release and Isaac Sim.
Note
The following changes are with respect to Isaac Lab 1.0 release. Please refer to the release notes for any changes in the future releases.
Task Config Setup#
In IsaacGymEnvs, task config files were defined in .yaml
format. With Isaac Lab, configs are now specified using
a specialized Python class configclass
. The configclass
module provides a wrapper on top of Python’s dataclasses
module. Each environment should specify its own config
class annotated by @configclass
that inherits from DirectRLEnvCfg
, which can include simulation
parameters, environment scene parameters, robot parameters, and task-specific parameters.
Below is an example skeleton of a task config class:
from omni.isaac.lab.envs import DirectRLEnvCfg
from omni.isaac.lab.scene import InteractiveSceneCfg
from omni.isaac.lab.sim import SimulationCfg
@configclass
class MyEnvCfg(DirectRLEnvCfg):
# simulation
sim: SimulationCfg = SimulationCfg()
# robot
robot_cfg: ArticulationCfg = ArticulationCfg()
# scene
scene: InteractiveSceneCfg = InteractiveSceneCfg()
# env
decimation = 2
episode_length_s = 5.0
action_space = 1
observation_space = 4
state_space = 0
# task-specific parameters
...
Simulation Config#
Simulation related parameters are defined as part of the SimulationCfg
class,
which is a configclass
module that holds simulation parameters such as dt
,
device
, and gravity
. Each task config must have a variable named sim
defined that holds the type
SimulationCfg
.
In Isaac Lab, the use of substeps
has been replaced
by a combination of the simulation dt
and the decimation
parameters. For example, in IsaacGymEnvs, having
dt=1/60
and substeps=2
is equivalent to taking 2 simulation steps with dt=1/120
, but running the task step
at 1/60
seconds. The decimation
parameter is a task parameter that controls the number of simulation steps to
take for each task (or RL) step, replacing the controlFrequencyInv
parameter in IsaacGymEnvs.
Thus, the same setup in Isaac Lab will become dt=1/120
and decimation=2
.
In Isaac Sim, physx simulation parameters such as num_position_iterations
, num_velocity_iterations
,
contact_offset
, rest_offset
, bounce_threshold_velocity
, max_depenetration_velocity
can all
be specified on a per-actor basis. These parameters have been moved from the physx simulation config
to each individual articulation and rigid body config.
When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing
information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted
depending on the complexity of the environment, the number of expected contacts and collisions,
and the number of actors in the environment. The PhysxCfg
class provides access for
setting the GPU buffer dimensions.
# IsaacGymEnvs
sim:
dt: 0.0166 # 1/60 s
substeps: 2
up_axis: "z"
use_gpu_pipeline: ${eq:${...pipeline},"gpu"}
gravity: [0.0, 0.0, -9.81]
physx:
num_threads: ${....num_threads}
solver_type: ${....solver_type}
use_gpu: ${contains:"cuda",${....sim_device}}
num_position_iterations: 4
num_velocity_iterations: 0
contact_offset: 0.02
rest_offset: 0.001
bounce_threshold_velocity: 0.2
max_depenetration_velocity: 100.0
default_buffer_size_multiplier: 2.0
max_gpu_contact_pairs: 1048576 # 1024*1024
num_subscenes: ${....num_subscenes}
contact_collection: 0
|
# IsaacLab
sim: SimulationCfg = SimulationCfg(
device = "cuda:0" # can be "cpu", "cuda", "cuda:<device_id>"
dt=1 / 120,
# decimation will be set in the task config
# up axis will always be Z in isaac sim
# use_gpu_pipeline is deduced from the device
gravity=(0.0, 0.0, -9.81),
physx: PhysxCfg = PhysxCfg(
# num_threads is no longer needed
solver_type=1,
# use_gpu is deduced from the device
max_position_iteration_count=4,
max_velocity_iteration_count=0,
# moved to actor config
# moved to actor config
bounce_threshold_velocity=0.2,
# moved to actor config
# default_buffer_size_multiplier is no longer needed
gpu_max_rigid_contact_count=2**23
# num_subscenes is no longer needed
# contact_collection is no longer needed
))
|
Scene Config#
The InteractiveSceneCfg
class can be used to specify parameters related to the scene,
such as the number of environments and the spacing between environments. Each task config must have a variable named
scene
defined that holds the type InteractiveSceneCfg
.
# IsaacGymEnvs
env:
numEnvs: ${resolve_default:512,${...num_envs}}
envSpacing: 4.0
|
# IsaacLab
scene: InteractiveSceneCfg = InteractiveSceneCfg(
num_envs=512,
env_spacing=4.0)
|
Task Config#
Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the observation and action buffers. Reward term scaling parameters can also be specified in the config class.
The following parameters must be set for each environment config:
decimation = 2
episode_length_s = 5.0
action_space = 1
observation_space = 4
state_space = 0
Note that the maximum episode length parameter (now episode_length_s
) is in seconds instead of steps as it was
in IsaacGymEnvs. To convert between step count to seconds, use the equation:
episode_length_s = dt * decimation * num_steps
RL Config Setup#
RL config files for the rl_games library can continue to be defined in .yaml
files in Isaac Lab.
Most of the content of the config file can be copied directly from IsaacGymEnvs.
Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files.
Please replace any relative paths such as ${....device}
with the actual values of the parameters.
Additionally, the observation and action clip ranges have been moved to the RL config file.
For any clipObservations
and clipActions
parameters that were defined in the IsaacGymEnvs task config file,
they should be moved to the RL config file in Isaac Lab.
IsaacGymEnvs Task Config |
Isaac Lab RL Config |
# IsaacGymEnvs
env:
clipObservations: 5.0
clipActions: 1.0
|
# IsaacLab
params:
env:
clip_observations: 5.0
clip_actions: 1.0
|
Environment Creation#
In IsaacGymEnvs, environment creation generally included four components: creating the sim object with create_sim()
,
creating the ground plane, importing the assets from MJCF or URDF files, and finally creating the environments
by looping through each environment and adding actors into the environments.
Isaac Lab no longer requires calling the create_sim()
method to retrieve the sim object. Instead, the simulation
context is retrieved automatically by the framework. It is also no longer required to use the sim
as an
argument for the simulation APIs.
In replacement of create_sim()
, tasks can implement the _setup_scene()
method in Isaac Lab.
This method can be used for adding actors into the scene, adding ground plane, cloning the actors, and
adding any other optional objects into the scene, such as lights.
IsaacGymEnvs |
Isaac Lab |
def create_sim(self):
# set the up axis to be z-up
self.up_axis = self.cfg["sim"]["up_axis"]
self.sim = super().create_sim(self.device_id, self.graphics_device_id,
self.physics_engine, self.sim_params)
self._create_ground_plane()
self._create_envs(self.num_envs, self.cfg["env"]['envSpacing'],
int(np.sqrt(self.num_envs)))
|
def _setup_scene(self):
self.cartpole = Articulation(self.cfg.robot_cfg)
# add ground plane
spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg()
# clone, filter, and replicate
self.scene.clone_environments(copy_from_source=False)
self.scene.filter_collisions(global_prim_paths=[])
# add articulation to scene
self.scene.articulations["cartpole"] = self.cartpole
# add lights
light_cfg = sim_utils.DomeLightCfg(intensity=2000.0)
light_cfg.func("/World/Light", light_cfg)
|
Ground Plane#
In Isaac Lab, most of the environment creation process has been simplified into configs with the configclass
module.
The ground plane can be defined using the TerrainImporterCfg
class.
from omni.isaac.lab.terrains import TerrainImporterCfg
terrain = TerrainImporterCfg(
prim_path="/World/ground",
terrain_type="plane",
collision_group=-1,
physics_material=sim_utils.RigidBodyMaterialCfg(
friction_combine_mode="multiply",
restitution_combine_mode="multiply",
static_friction=1.0,
dynamic_friction=1.0,
restitution=0.0,
),
)
The terrain can then be added to the scene in _setup_scene(self)
by referencing the TerrainImporterCfg
object:
Actors#
Isaac Lab and Isaac Sim both use the USD (Universal Scene Description) library for describing the scene. Assets defined in MJCF and URDF formats can be imported to USD using importer tools described in the Importing a New Asset tutorial.
Each Articulation and Rigid Body actor can also have its own config class. The
ArticulationCfg
class can be used to define parameters for articulation actors,
including file path, simulation parameters, actuator properties, and initial states.
Within the ArticulationCfg
, the spawn
attribute can be used to add the robot to the scene by
specifying the path to the robot file. In addition, RigidBodyPropertiesCfg
can
be used to specify simulation properties for the rigid bodies in the articulation.
Similarly, the ArticulationRootPropertiesCfg
class can be used to specify
simulation properties for the articulation. Joint properties are now specified as part of the actuators
dictionary using ImplicitActuatorCfg
. Joints with the same properties can be grouped into
regex expressions or provided as a list of names or expressions.
Actors are added to the scene by simply calling self.cartpole = Articulation(self.cfg.robot_cfg)
,
where self.cfg.robot_cfg
is an ArticulationCfg
object. Once initialized, they should also
be added to the InteractiveScene
by calling self.scene.articulations["cartpole"] = self.cartpole
so that the InteractiveScene
can traverse through actors in the scene for writing values to the
simulation and resetting.
Simulation Parameters for Actors#
Some simulation parameters related to Rigid Bodies and Articulations may have different default values between Isaac Gym Preview Release and Isaac Sim. It may be helpful to double check the USD assets to ensure that the default values are applicable for the asset.
For instance, the following parameters in the RigidBodyAPI
could be different
between Isaac Gym Preview Release and Isaac Sim:
RigidBodyAPI Parameter |
Default Value in Isaac Sim |
Default Value in Isaac Gym Preview Release |
---|---|---|
Linear Damping |
0.00 |
0.00 |
Angular Damping |
0.05 |
0.0 |
Max Linear Velocity |
inf |
1000 |
Max Angular Velocity |
5729.58008 (degree/s) |
64.0 (rad/s) |
Max Contact Impulse |
inf |
1e32 |
Articulation parameters for the JointAPI
and DriveAPI
could be altered as well. Note
that the Isaac Sim UI assumes the unit of angle to be degrees. It is particularly
worth noting that the Damping
and Stiffness
parameters in the DriveAPI
have the unit
of 1/deg
in the Isaac Sim UI but 1/rad
in Isaac Gym Preview Release.
Joint Parameter |
Default Value in Isaac Sim |
Default Value in Isaac Gym Preview Releases |
---|---|---|
Maximum Joint Velocity |
1000000.0 (deg) |
100.0 (rad) |
Cloner#
Isaac Sim introduced a concept of Cloner
, which is a class designed for replication during the scene creation process.
In IsaacGymEnvs, scenes had to be created by looping through the number of environments.
Within each iteration, actors were added to each environment and their handles had to be cached.
Isaac Lab eliminates the need for looping through the environments by using the Cloner
APIs.
The scene creation process is as follow:
Construct a single environment (what the scene would look like if number of environments = 1)
Call
clone_environments()
to replicate the single environmentCall
filter_collisions()
to filter out collision between environments (if required)
# construct a single environment with the Cartpole robot
self.cartpole = Articulation(self.cfg.robot_cfg)
# clone the environment
self.scene.clone_environments(copy_from_source=False)
# filter collisions
self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path])
Accessing States from Simulation#
APIs for accessing physics states in Isaac Lab require the creation of an Articulation
or RigidObject
object. Multiple objects can be initialized for different articulations or rigid bodies in the scene by defining
corresponding ArticulationCfg
or RigidObjectCfg
config as outlined in the section above.
This approach eliminates the need of retrieving body handles to slice states for specific bodies in the scene.
self._robot = Articulation(self.cfg.robot)
self._cabinet = Articulation(self.cfg.cabinet)
self._object = RigidObject(self.cfg.object_cfg)
We have also removed acquire
and refresh
APIs in Isaac Lab. Physics states can be directly applied or retrieved
using APIs defined for the articulations and rigid objects.
APIs provided in Isaac Lab no longer require explicit wrapping and un-wrapping of underlying buffers. APIs can now work with tensors directly for reading and writing data.
IsaacGymEnvs |
Isaac Lab |
dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim)
self.dof_state = gymtorch.wrap_tensor(dof_state_tensor)
self.gym.refresh_dof_state_tensor(self.sim)
|
self.joint_pos = self._robot.data.joint_pos
self.joint_vel = self._robot.data.joint_vel
|
Note some naming differences between APIs in Isaac Gym Preview Release and Isaac Lab. Most dof
related APIs have been
named to joint
in Isaac Lab.
APIs in Isaac Lab also no longer follow the explicit _tensors
or _tensor_indexed
suffixes in naming.
Indexed versions of APIs now happen implicitly through the optional indices
parameter.
Most APIs in Isaac Lab also provide
the option to specify an indices
parameter, which can be used when reading or writing data for a subset
of environments. Note that when setting states with the indices
parameter, the shape of the states buffer
should match with the dimension of the indices
list.
IsaacGymEnvs |
Isaac Lab |
env_ids_int32 = env_ids.to(dtype=torch.int32)
self.gym.set_dof_state_tensor_indexed(self.sim,
gymtorch.unwrap_tensor(self.dof_state),
gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32))
|
self._robot.write_joint_state_to_sim(joint_pos, joint_vel,
joint_ids, env_ids)
|
Quaternion Convention#
Isaac Lab and Isaac Sim both adopt wxyz
as the quaternion convention. However, the quaternion
convention used in Isaac Gym Preview Release was xyzw
.
Remember to switch all quaternions to use the xyzw
convention when working indexing rotation data.
Similarly, please ensure all quaternions are in wxyz
before passing them to Isaac Lab APIs.
Articulation Joint Order#
Physics simulation in Isaac Sim and Isaac Lab assumes a breadth-first ordering for the joints in a given kinematic tree. However, Isaac Gym Preview Release assumed a depth-first ordering for joints in the kinematic tree. This means that indexing joints based on their ordering may be different in IsaacGymEnvs and Isaac Lab.
In Isaac Lab, the list of joint names can be retrieved with Articulation.data.joint_names
, which will
also correspond to the ordering of the joints in the Articulation.
Creating a New Environment#
Each environment in Isaac Lab should be in its own directory following this structure:
my_environment/
- agents/
- __init__.py
- rl_games_ppo_cfg.py
- __init__.py
my_env.py
my_environment
is the root directory of the task.my_environment/agents
is the directory containing all RL config files for the task. Isaac Lab supports multiple RL libraries that can each have its own individual config file.my_environment/__init__.py
is the main file that registers the environment with the Gymnasium interface. This allows the training and inferencing scripts to find the task by its name. The content of this file should be as follow:
import gymnasium as gym
from . import agents
from .cartpole_env import CartpoleEnv, CartpoleEnvCfg
##
# Register Gym environments.
##
gym.register(
id="Isaac-Cartpole-Direct-v0",
entry_point="omni.isaac.lab_tasks.direct_workflow.cartpole:CartpoleEnv",
disable_env_checker=True,
kwargs={
"env_cfg_entry_point": CartpoleEnvCfg,
"rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml"
},
)
my_environment/my_env.py
is the main python script that implements the task logic and task config class for the environment.
Task Logic#
In Isaac Lab, the post_physics_step
function has been moved to the framework in the base class.
Tasks are not required to implement this method, but can choose to override it if a different workflow is desired.
By default, Isaac Lab follows the following flow in logic:
IsaacGymEnvs |
Isaac Lab |
pre_physics_step
|-- apply_action
post_physics_step
|-- reset_idx()
|-- compute_observation()
|-- compute_reward()
|
pre_physics_step
|-- _pre_physics_step(action)
|-- _apply_action()
post_physics_step
|-- _get_dones()
|-- _get_rewards()
|-- _reset_idx()
|-- _get_observations()
|
In Isaac Lab, we also separate the pre_physics_step
API for processing actions from the policy with
the apply_action
API, which sets the actions into the simulation. This provides more flexibility in controlling
when actions should be written to simulation when decimation
is used.
pre_physics_step
will be called once per step before stepping simulation.
apply_actions
will be called decimation
number of times for each RL step, once before each simulation step call.
With this approach, resets are performed based on actions from the current step instead of the previous step. Observations will also be computed with the correct states after resets.
We have also performed some renamings of APIs:
create_sim(self)
–>_setup_scene(self)
pre_physics_step(self, actions)
–>_pre_physics_step(self, actions)
and_apply_action(self)
reset_idx(self, env_ids)
–>_reset_idx(self, env_ids)
compute_observations(self)
–>_get_observations(self)
-_get_observations()
should now return a dictionary{"policy": obs}
compute_reward(self)
–>_get_rewards(self)
-_get_rewards()
should now return the reward bufferpost_physics_step(self)
–> moved to the base classIn addition, Isaac Lab requires the implementation of
_is_done(self)
, which should return two buffers: thereset
buffer and thetime_out
buffer.
Putting It All Together#
The Cartpole environment is shown here in completion to fully show the comparison between the IsaacGymEnvs implementation and the Isaac Lab implementation.
Task Config#
IsaacGymEnvs |
Isaac Lab |
# used to create the object
name: Cartpole
physics_engine: ${..physics_engine}
# if given, will override the device setting in gym.
env:
numEnvs: ${resolve_default:512,${...num_envs}}
envSpacing: 4.0
resetDist: 3.0
maxEffort: 400.0
clipObservations: 5.0
clipActions: 1.0
asset:
assetRoot: "../../assets"
assetFileName: "urdf/cartpole.urdf"
enableCameraSensors: False
sim:
dt: 0.0166 # 1/60 s
substeps: 2
up_axis: "z"
use_gpu_pipeline: ${eq:${...pipeline},"gpu"}
gravity: [0.0, 0.0, -9.81]
physx:
num_threads: ${....num_threads}
solver_type: ${....solver_type}
use_gpu: ${contains:"cuda",${....sim_device}}
num_position_iterations: 4
num_velocity_iterations: 0
contact_offset: 0.02
rest_offset: 0.001
bounce_threshold_velocity: 0.2
max_depenetration_velocity: 100.0
default_buffer_size_multiplier: 2.0
max_gpu_contact_pairs: 1048576 # 1024*1024
num_subscenes: ${....num_subscenes}
contact_collection: 0
|
@configclass
class CartpoleEnvCfg(DirectRLEnvCfg):
# simulation
sim: SimulationCfg = SimulationCfg(dt=1 / 120)
# robot
robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace(
prim_path="/World/envs/env_.*/Robot")
cart_dof_name = "slider_to_cart"
pole_dof_name = "cart_to_pole"
# scene
scene: InteractiveSceneCfg = InteractiveSceneCfg(
num_envs=4096, env_spacing=4.0, replicate_physics=True)
# env
decimation = 2
episode_length_s = 5.0
action_scale = 100.0 # [N]
action_space = 1
observation_space = 4
state_space = 0
# reset
max_cart_pos = 3.0
initial_pole_angle_range = [-0.25, 0.25]
# reward scales
rew_scale_alive = 1.0
rew_scale_terminated = -2.0
rew_scale_pole_pos = -1.0
rew_scale_cart_vel = -0.01
rew_scale_pole_vel = -0.005
|
Task Setup#
Isaac Lab no longer requires pre-initialization of buffers through the acquire_*
APIs that were used in IsaacGymEnvs.
It is also no longer necessary to wrap
and unwrap
tensors.
IsaacGymEnvs |
Isaac Lab |
class Cartpole(VecTask):
def __init__(self, cfg, rl_device, sim_device, graphics_device_id,
headless, virtual_screen_capture, force_render):
self.cfg = cfg
self.reset_dist = self.cfg["env"]["resetDist"]
self.max_push_effort = self.cfg["env"]["maxEffort"]
self.max_episode_length = 500
self.cfg["env"]["numObservations"] = 4
self.cfg["env"]["numActions"] = 1
super().__init__(config=self.cfg,
rl_device=rl_device, sim_device=sim_device,
graphics_device_id=graphics_device_id, headless=headless,
virtual_screen_capture=virtual_screen_capture,
force_render=force_render)
dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim)
self.dof_state = gymtorch.wrap_tensor(dof_state_tensor)
self.dof_pos = self.dof_state.view(
self.num_envs, self.num_dof, 2)[..., 0]
self.dof_vel = self.dof_state.view(
self.num_envs, self.num_dof, 2)[..., 1]
|
class CartpoleEnv(DirectRLEnv):
cfg: CartpoleEnvCfg
def __init__(self, cfg: CartpoleEnvCfg,
render_mode: str | None = None, **kwargs):
super().__init__(cfg, render_mode, **kwargs)
self._cart_dof_idx, _ = self.cartpole.find_joints(
self.cfg.cart_dof_name)
self._pole_dof_idx, _ = self.cartpole.find_joints(
self.cfg.pole_dof_name)
self.action_scale = self.cfg.action_scale
self.joint_pos = self.cartpole.data.joint_pos
self.joint_vel = self.cartpole.data.joint_vel
|
Scene Setup#
Scene setup is now done through the Cloner
API and by specifying actor attributes in config objects.
This eliminates the need to loop through the number of environments to set up the environments and avoids
the need to set simulation parameters for actors in the task implementation.
IsaacGymEnvs |
Isaac Lab |
def create_sim(self):
# set the up axis to be z-up given that assets are y-up by default
self.up_axis = self.cfg["sim"]["up_axis"]
self.sim = super().create_sim(self.device_id,
self.graphics_device_id, self.physics_engine,
self.sim_params)
self._create_ground_plane()
self._create_envs(self.num_envs,
self.cfg["env"]['envSpacing'],
int(np.sqrt(self.num_envs)))
def _create_ground_plane(self):
plane_params = gymapi.PlaneParams()
# set the normal force to be z dimension
plane_params.normal = (gymapi.Vec3(0.0, 0.0, 1.0)
if self.up_axis == 'z'
else gymapi.Vec3(0.0, 1.0, 0.0))
self.gym.add_ground(self.sim, plane_params)
def _create_envs(self, num_envs, spacing, num_per_row):
# define plane on which environments are initialized
lower = (gymapi.Vec3(0.5 * -spacing, -spacing, 0.0)
if self.up_axis == 'z'
else gymapi.Vec3(0.5 * -spacing, 0.0, -spacing))
upper = gymapi.Vec3(0.5 * spacing, spacing, spacing)
asset_root = os.path.join(os.path.dirname(
os.path.abspath(__file__)), "../../assets")
asset_file = "urdf/cartpole.urdf"
if "asset" in self.cfg["env"]:
asset_root = os.path.join(os.path.dirname(
os.path.abspath(__file__)),
self.cfg["env"]["asset"].get("assetRoot", asset_root))
asset_file = self.cfg["env"]["asset"].get(
"assetFileName", asset_file)
asset_path = os.path.join(asset_root, asset_file)
asset_root = os.path.dirname(asset_path)
asset_file = os.path.basename(asset_path)
asset_options = gymapi.AssetOptions()
asset_options.fix_base_link = True
cartpole_asset = self.gym.load_asset(self.sim,
asset_root, asset_file, asset_options)
self.num_dof = self.gym.get_asset_dof_count(
cartpole_asset)
pose = gymapi.Transform()
if self.up_axis == 'z':
pose.p.z = 2.0
pose.r = gymapi.Quat(0.0, 0.0, 0.0, 1.0)
else:
pose.p.y = 2.0
pose.r = gymapi.Quat(
-np.sqrt(2)/2, 0.0, 0.0, np.sqrt(2)/2)
self.cartpole_handles = []
self.envs = []
for i in range(self.num_envs):
# create env instance
env_ptr = self.gym.create_env(
self.sim, lower, upper, num_per_row
)
cartpole_handle = self.gym.create_actor(
env_ptr, cartpole_asset, pose,
"cartpole", i, 1, 0)
dof_props = self.gym.get_actor_dof_properties(
env_ptr, cartpole_handle)
dof_props['driveMode'][0] = gymapi.DOF_MODE_EFFORT
dof_props['driveMode'][1] = gymapi.DOF_MODE_NONE
dof_props['stiffness'][:] = 0.0
dof_props['damping'][:] = 0.0
self.gym.set_actor_dof_properties(env_ptr, c
artpole_handle, dof_props)
self.envs.append(env_ptr)
self.cartpole_handles.append(cartpole_handle)
|
def _setup_scene(self):
self.cartpole = Articulation(self.cfg.robot_cfg)
# add ground plane
spawn_ground_plane(prim_path="/World/ground",
cfg=GroundPlaneCfg())
# clone, filter, and replicate
self.scene.clone_environments(
copy_from_source=False)
self.scene.filter_collisions(
global_prim_paths=[])
# add articulation to scene
self.scene.articulations["cartpole"] = self.cartpole
# add lights
light_cfg = sim_utils.DomeLightCfg(
intensity=2000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/Light", light_cfg)
CARTPOLE_CFG = ArticulationCfg(
spawn=sim_utils.UsdFileCfg(
usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd",
rigid_props=sim_utils.RigidBodyPropertiesCfg(
rigid_body_enabled=True,
max_linear_velocity=1000.0,
max_angular_velocity=1000.0,
max_depenetration_velocity=100.0,
enable_gyroscopic_forces=True,
),
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=False,
solver_position_iteration_count=4,
solver_velocity_iteration_count=0,
sleep_threshold=0.005,
stabilization_threshold=0.001,
),
),
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 2.0),
joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0}
),
actuators={
"cart_actuator": ImplicitActuatorCfg(
joint_names_expr=["slider_to_cart"],
effort_limit=400.0,
velocity_limit=100.0,
stiffness=0.0,
damping=10.0,
),
"pole_actuator": ImplicitActuatorCfg(
joint_names_expr=["cart_to_pole"], effort_limit=400.0,
velocity_limit=100.0, stiffness=0.0, damping=0.0
),
},
)
|
Pre and Post Physics Step#
In IsaacGymEnvs, due to limitations of the GPU APIs, observations had stale data when environments had to perform resets.
This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, stepping simulation,
collecting states, computing dones, calculating rewards, performing resets, and finally computing observations.
This workflow is done automatically by the framework such that a post_physics_step
API is not required in the task.
However, individual tasks can override the step()
API to control the workflow.
IsaacGymEnvs |
IsaacLab |
def pre_physics_step(self, actions):
actions_tensor = torch.zeros(
self.num_envs * self.num_dof,
device=self.device, dtype=torch.float)
actions_tensor[::self.num_dof] = actions.to(
self.device).squeeze() * self.max_push_effort
forces = gymtorch.unwrap_tensor(actions_tensor)
self.gym.set_dof_actuation_force_tensor(
self.sim, forces)
def post_physics_step(self):
self.progress_buf += 1
env_ids = self.reset_buf.nonzero(
as_tuple=False).squeeze(-1)
if len(env_ids) > 0:
self.reset_idx(env_ids)
self.compute_observations()
self.compute_reward()
|
def _pre_physics_step(self, actions: torch.Tensor) -> None:
self.actions = self.action_scale * actions
def _apply_action(self) -> None:
self.cartpole.set_joint_effort_target(
self.actions, joint_ids=self._cart_dof_idx)
|
Dones and Resets#
In Isaac Lab, dones
are computed in the _get_dones()
method and should return two variables: resets
and time_out
.
Tracking of the progress_buf
has been moved to the base class and is now automatically incremented and reset by the framework.
The progress_buf
variable has also been renamed to episode_length_buf
.
IsaacGymEnvs |
Isaac Lab |
def reset_idx(self, env_ids):
positions = 0.2 * (torch.rand((len(env_ids), self.num_dof),
device=self.device) - 0.5)
velocities = 0.5 * (torch.rand((len(env_ids), self.num_dof),
device=self.device) - 0.5)
self.dof_pos[env_ids, :] = positions[:]
self.dof_vel[env_ids, :] = velocities[:]
env_ids_int32 = env_ids.to(dtype=torch.int32)
self.gym.set_dof_state_tensor_indexed(self.sim,
gymtorch.unwrap_tensor(self.dof_state),
gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32))
self.reset_buf[env_ids] = 0
self.progress_buf[env_ids] = 0
|
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
self.joint_pos = self.cartpole.data.joint_pos
self.joint_vel = self.cartpole.data.joint_vel
time_out = self.episode_length_buf >= self.max_episode_length - 1
out_of_bounds = torch.any(torch.abs(
self.joint_pos[:, self._cart_dof_idx]) > self.cfg.max_cart_pos,
dim=1)
out_of_bounds = out_of_bounds | torch.any(
torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2,
dim=1)
return out_of_bounds, time_out
def _reset_idx(self, env_ids: Sequence[int] | None):
if env_ids is None:
env_ids = self.cartpole._ALL_INDICES
super()._reset_idx(env_ids)
joint_pos = self.cartpole.data.default_joint_pos[env_ids]
joint_pos[:, self._pole_dof_idx] += sample_uniform(
self.cfg.initial_pole_angle_range[0] * math.pi,
self.cfg.initial_pole_angle_range[1] * math.pi,
joint_pos[:, self._pole_dof_idx].shape,
joint_pos.device,
)
joint_vel = self.cartpole.data.default_joint_vel[env_ids]
default_root_state = self.cartpole.data.default_root_state[env_ids]
default_root_state[:, :3] += self.scene.env_origins[env_ids]
self.joint_pos[env_ids] = joint_pos
self.cartpole.write_root_link_pose_to_sim(
default_root_state[:, :7], env_ids)
self.cartpole.write_root_com_velocity_to_sim(
default_root_state[:, 7:], env_ids)
self.cartpole.write_joint_state_to_sim(
joint_pos, joint_vel, None, env_ids)
|
Observations#
In Isaac Lab, the _get_observations()
API should now return a dictionary containing the policy
key with the observation
buffer as the value.
For asymmetric policies, the dictionary should also include a critic
key that holds the state buffer.
IsaacGymEnvs |
Isaac Lab |
def compute_observations(self, env_ids=None):
if env_ids is None:
env_ids = np.arange(self.num_envs)
self.gym.refresh_dof_state_tensor(self.sim)
self.obs_buf[env_ids, 0] = self.dof_pos[env_ids, 0]
self.obs_buf[env_ids, 1] = self.dof_vel[env_ids, 0]
self.obs_buf[env_ids, 2] = self.dof_pos[env_ids, 1]
self.obs_buf[env_ids, 3] = self.dof_vel[env_ids, 1]
return self.obs_buf
|
def _get_observations(self) -> dict:
obs = torch.cat(
(
self.joint_pos[:, self._pole_dof_idx[0]],
self.joint_vel[:, self._pole_dof_idx[0]],
self.joint_pos[:, self._cart_dof_idx[0]],
self.joint_vel[:, self._cart_dof_idx[0]],
),
dim=-1,
)
observations = {"policy": obs}
return observations
|
Rewards#
In Isaac Lab, the reward method _get_rewards
should return the reward buffer as a return value.
Similar to IsaacGymEnvs, computations in the reward function can also be performed using pytorch jit
by adding the @torch.jit.script
annotation.
IsaacGymEnvs |
Isaac Lab |
def compute_reward(self):
# retrieve environment observations from buffer
pole_angle = self.obs_buf[:, 2]
pole_vel = self.obs_buf[:, 3]
cart_vel = self.obs_buf[:, 1]
cart_pos = self.obs_buf[:, 0]
self.rew_buf[:], self.reset_buf[:] = compute_cartpole_reward(
pole_angle, pole_vel, cart_vel, cart_pos,
self.reset_dist, self.reset_buf,
self.progress_buf, self.max_episode_length
)
@torch.jit.script
def compute_cartpole_reward(pole_angle, pole_vel,
cart_vel, cart_pos,
reset_dist, reset_buf,
progress_buf, max_episode_length):
reward = (1.0 - pole_angle * pole_angle -
0.01 * torch.abs(cart_vel) -
0.005 * torch.abs(pole_vel))
# adjust reward for reset agents
reward = torch.where(torch.abs(cart_pos) > reset_dist,
torch.ones_like(reward) * -2.0, reward)
reward = torch.where(torch.abs(pole_angle) > np.pi / 2,
torch.ones_like(reward) * -2.0, reward)
reset = torch.where(torch.abs(cart_pos) > reset_dist,
torch.ones_like(reset_buf), reset_buf)
reset = torch.where(torch.abs(pole_angle) > np.pi / 2,
torch.ones_like(reset_buf), reset_buf)
reset = torch.where(progress_buf >= max_episode_length - 1,
torch.ones_like(reset_buf), reset)
|
def _get_rewards(self) -> torch.Tensor:
total_reward = compute_rewards(
self.cfg.rew_scale_alive,
self.cfg.rew_scale_terminated,
self.cfg.rew_scale_pole_pos,
self.cfg.rew_scale_cart_vel,
self.cfg.rew_scale_pole_vel,
self.joint_pos[:, self._pole_dof_idx[0]],
self.joint_vel[:, self._pole_dof_idx[0]],
self.joint_pos[:, self._cart_dof_idx[0]],
self.joint_vel[:, self._cart_dof_idx[0]],
self.reset_terminated,
)
return total_reward
@torch.jit.script
def compute_rewards(
rew_scale_alive: float,
rew_scale_terminated: float,
rew_scale_pole_pos: float,
rew_scale_cart_vel: float,
rew_scale_pole_vel: float,
pole_pos: torch.Tensor,
pole_vel: torch.Tensor,
cart_pos: torch.Tensor,
cart_vel: torch.Tensor,
reset_terminated: torch.Tensor,
):
rew_alive = rew_scale_alive * (1.0 - reset_terminated.float())
rew_termination = rew_scale_terminated * reset_terminated.float()
rew_pole_pos = rew_scale_pole_pos * torch.sum(
torch.square(pole_pos), dim=-1)
rew_cart_vel = rew_scale_cart_vel * torch.sum(
torch.abs(cart_vel), dim=-1)
rew_pole_vel = rew_scale_pole_vel * torch.sum(
torch.abs(pole_vel), dim=-1)
total_reward = (rew_alive + rew_termination
+ rew_pole_pos + rew_cart_vel + rew_pole_vel)
return total_reward
|
Launching Training#
To launch a training in Isaac Lab, use the command:
python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless
Launching Inferencing#
To launch inferencing in Isaac Lab, use the command:
python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint=<path/to/checkpoint>