从OmniIsaacGymEnvs迁移#
OmniIsaacGymEnvs 是一个使用 Isaac Sim 平台的强化学习框架。来自 OmniIsaacGymEnvs 的功能已经整合到 Isaac Lab 框架中。我们已经更新了 OmniIsaacGymEnvs 到 Isaac Sim 版本 4.0.0,以支持迁移到 Isaac Lab 的过程。今后,OmniIsaacGymEnvs 将被弃用,未来的开发将继续在 Isaac Lab 中进行。
备注
以下更改针对于 Isaac Lab 1.0 发布版本。请参考 release notes 以获取未来发布版本的任何更改信息。
任务配置设置#
在 OmniIsaacGymEnvs中,任务配置文件是以 .yaml
格式定义的。而使用 Isaac Lab,配置现在使用专门的 Python 类 configclass
来指定。 configclass
模块提供了对 Python 的 dataclasses
模块的包装。每个环境都应该指定自己的配置类,配置类使用 @configclass
注释,并继承自 DirectRLEnvCfg
类,其中可以包括模拟参数、环境场景参数、机器人参数和特定于任务的参数。
下面是任务配置类的示例骨架:
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
...
模拟配置#
与模拟相关的参数是作为 SimulationCfg
类的一部分进行定义的,这是一个 configclass
模块,包含模拟参数,如 dt
, device
和 gravity
。每个任务配置必须定义一个名为 sim
的变量,它包含类型 SimulationCfg
。
关节和刚体的仿真参数,例如 num_position_iterations
、 num_velocity_iterations
、 contact_offset
、 rest_offset
、 bounce_threshold_velocity
和 max_depenetration_velocity
,可以在每个单独的关节和刚体的配置类中以每个角色为基础进行指定。
在使用 GPU 运行仿真时,PhysX 中的缓冲区需要预先分配,用于计算和存储诸如接触、碰撞和聚合对等信息。这些缓冲区可能需要根据环境复杂性、预期接触和碰撞数量以及环境中的演员数量进行调整。PhysxCfg
类提供了设置 GPU 缓冲区尺寸的访问功能。
# OmniIsaacGymEnvs
sim:
dt: 0.0083 # 1/120 s
use_gpu_pipeline: ${eq:${...pipeline},"gpu"}
use_fabric: True
enable_scene_query_support: False
disable_contact_processing: False
gravity: [0.0, 0.0, -9.81]
default_physics_material:
static_friction: 1.0
dynamic_friction: 1.0
restitution: 0.0
physx:
worker_thread_count: ${....num_threads}
solver_type: ${....solver_type}
use_gpu: ${contains:"cuda",${....sim_device}}
solver_position_iteration_count: 4
solver_velocity_iteration_count: 0
contact_offset: 0.02
rest_offset: 0.001
bounce_threshold_velocity: 0.2
friction_offset_threshold: 0.04
friction_correlation_distance: 0.025
enable_sleeping: True
enable_stabilization: True
max_depenetration_velocity: 100.0
gpu_max_rigid_contact_count: 524288
gpu_max_rigid_patch_count: 81920
gpu_found_lost_pairs_capacity: 1024
gpu_found_lost_aggregate_pairs_capacity: 262144
gpu_total_aggregate_pairs_capacity: 1024
gpu_heap_capacity: 67108864
gpu_temp_buffer_capacity: 16777216
gpu_max_num_partitions: 8
gpu_max_soft_body_contacts: 1048576
gpu_max_particle_contacts: 1048576
|
# IsaacLab
sim: SimulationCfg = SimulationCfg(
device = "cuda:0" # can be "cpu", "cuda", "cuda:<device_id>"
dt=1 / 120,
# use_gpu_pipeline is deduced from the device
use_fabric=True,
enable_scene_query_support=False,
disable_contact_processing=False,
gravity=(0.0, 0.0, -9.81),
physics_material=RigidBodyMaterialCfg(
static_friction=1.0,
dynamic_friction=1.0,
restitution=0.0
)
physx: PhysxCfg = PhysxCfg(
# worker_thread_count 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,
friction_offset_threshold=0.04,
friction_correlation_distance=0.025,
# enable_sleeping is no longer needed
enable_stabilization=True,
# moved to RigidBodyPropertiesCfg
gpu_max_rigid_contact_count=2**23,
gpu_max_rigid_patch_count=5 * 2**15,
gpu_found_lost_pairs_capacity=2**21,
gpu_found_lost_aggregate_pairs_capacity=2**25,
gpu_total_aggregate_pairs_capacity=2**21,
gpu_heap_capacity=2**26,
gpu_temp_buffer_capacity=2**24,
gpu_max_num_partitions=8,
gpu_max_soft_body_contacts=2**20,
gpu_max_particle_contacts=2**20,
)
)
|
以下参数,如 add_ground_plane
和 add_distant_light
,现在在创建场景时已成为任务逻辑的一部分。enable_cameras
现在作为命令行参数 --enable_cameras
存在,可以直接传递给训练脚本。
场景配置#
InteractiveSceneCfg
类可用于指定与场景相关的参数,比如环境数量和环境之间的间距。每个任务配置必须定义一个名为 scene
的变量,其中包含类型 InteractiveSceneCfg
。
# OmniIsaacGymEnvs
env:
numEnvs: ${resolve_default:512,${...num_envs}}
envSpacing: 4.0
|
# IsaacLab
scene: InteractiveSceneCfg = InteractiveSceneCfg(
num_envs=512,
env_spacing=4.0)
|
任务配置#
每个环境都应该指定自己的配置类,该类包含特定于任务的参数,比如观察和行动缓冲区的维度。奖励项缩放参数也可以在配置类中进行指定。
在Isaac Lab中, controlFrequencyInv
参数已更名为 decimation
,必须在配置类中作为参数进行指定。
此外,最大的episode长度参数(现在是 episode_length_s
)以秒为单位,而不是作为在OmniIsaacGymEnvs中的步数。要在步数和秒数之间转换,使用以下方程:episode_length_s = dt * decimation * num_steps
。
下面的参数必须为每个环境配置设置:
decimation = 2
episode_length_s = 5.0
action_space = 1
observation_space = 4
state_space = 0
RL配置设置#
RL游戏库的rl配置文件在Isaac Lab中可以继续使用 .yaml
文件进行定义。配置文件的大部分内容可以直接从OmniIsaacGymEnvs中复制。请注意,在Isaac Lab中,我们不使用hydra来解析配置文件中的相对路径。请用参数的实际值替换任何相对路径,如 ${....device}
。
此外,观察和行动裁剪范围已经移到RL配置文件中。对于在IsaacGymEnvs任务配置文件中定义的 clipObservations
和 clipActions
参数,它们应该被移到Isaac Lab的RL配置文件中。
IsaacGymEnvs任务配置 |
Isaac Lab RL配置 |
# OmniIsaacGymEnvs
env:
clipObservations: 5.0
clipActions: 1.0
|
# IsaacLab
params:
env:
clip_observations: 5.0
clip_actions: 1.0
|
环境创建#
在OmniIsaacGymEnvs中,环境创建通常发生在 set_up_scene()
API中,其中涉及创建初始环境、克隆环境、过滤碰撞、添加地面和光线,并创建角色的 View
类。
在Isaac Lab中,类似的功能在 _setup_scene()
API中执行。主要区别在于基类 _setup_scene()
不再执行克隆环境和添加地面和光线的操作。相反,现在这些操作应该在各个任务的 _setup_scene
实现中进行,以提供更灵活的场景设置过程。
还要注意的是,通过定义 Articulation
或 RigidObject
对象,将根据角色配置中的 spawn
参数将角色添加到场景中,并且 View
类将自动为角色创建。这样可以避免需要单独为角色定义 ArticulationView
或 RigidPrimView
对象。
OmniIsaacGymEnvs |
Isaac Lab |
def set_up_scene(self, scene) -> None:
self.get_cartpole()
super().set_up_scene(scene)
self._cartpoles = ArticulationView(
prim_paths_expr="/World/envs/.*/Cartpole",
name="cartpole_view", reset_xform_properties=False
)
scene.add(self._cartpoles)
|
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)
|
地面#
除了以上示例外,还可以使用 TerrainImporterCfg
类定义更复杂的地面。
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,
),
)
然后,在 _setup_scene(self)
中,通过引用 TerrainImporterCfg
对象将地形添加到场景中:
角色#
在Isaac Lab中,每个关节和刚体角色都可以有自己的配置类。 ArticulationCfg
类可用于定义关节角色的参数,包括文件路径、模拟参数、执行器属性和初始状态。
在 ArticulationCfg
中, spawn
属性可用于通过指定机器人文件的路径将机器人添加到场景中。此外, RigidBodyPropertiesCfg
类可用于为关节中的刚体指定模拟属性。同样, ArticulationRootPropertiesCfg
类可用于指定关节的模拟属性。使用 ImplicitActuatorCfg
,关节属性现在作为 actuators
字典的一部分进行指定。具有相同属性的关节可以被分组成正则表达式或提供为名称或表达式的列表。
角色是通过简单地调用 self.cartpole = Articulation(self.cfg.robot_cfg)
进行添加到场景的,其中 self.cfg.robot_cfg
是一个 ArticulationCfg
对象。初始化后,它们也应该被添加到 InteractiveScene
中,通过调用 self.scene.articulations["cartpole"] = self.cartpole
,以便 InteractiveScene
可以遍历场景中的角色,以便向模拟写入值并进行重置。
从模拟中获取状态#
在Isaac Lab中,访问物理状态的API需要创建一个 Articulation
或 RigidObject
对象。可以通过定义相应的 ArticulationCfg
或 RigidObjectCfg
配置来为场景中的不同关节或刚体初始化多个对象,就像在上面的部分中所述的。这取代了以前在OmniIsaacGymEnvs中使用的 ArticulationView
和 omni.isaac.core.prims.RigidPrimView
类。
然而,这些类之间的功能是类似的:
OmniIsaacGymEnvs |
Isaac Lab |
dof_pos = self._cartpoles.get_joint_positions(clone=False)
dof_vel = self._cartpoles.get_joint_velocities(clone=False)
|
self.joint_pos = self._robot.data.joint_pos
self.joint_vel = self._robot.data.joint_vel
|
在Isaac Lab中, Articulation
和 RigidObject
类都有一个 data
类。数据类( ArticulationData
和 RigidObjectData
)包含用于保存关节和刚体状态的缓冲区,并提供了一种更高效的方式从角色获取状态。
除了一些API的重命名外,设置角色状态的功能在OmniIsaacGymEnvs和Isaac Lab之间也可以进行类似的操作。
OmniIsaacGymEnvs |
Isaac Lab |
indices = env_ids.to(dtype=torch.int32)
self._cartpoles.set_joint_positions(dof_pos, indices=indices)
self._cartpoles.set_joint_velocities(dof_vel, indices=indices)
|
self._robot.write_joint_state_to_sim(joint_pos, joint_vel,
joint_ids, env_ids)
|
在Isaac Lab中, root_pose
和 root_velocity
已合并为单个缓冲区,并且不再分割为 root_position
, root_orientation
, root_linear_velocity
和 root_angular_velocity
。
创建一个新环境#
Isaac Lab中的每个环境应该在自己的目录中,遵循以下结构:
my_environment/
- agents/
- __init__.py
- rl_games_ppo_cfg.py
- __init__.py
my_env.py
my_environment
是任务的根目录。my_environment/agents
是包含任务的所有RL配置文件的目录。Isaac Lab支持多个RL库,每个库可以有自己的配置文件。my_environment/__init__.py
是通过Gymnasium接口将环境注册到Gymnasium接口的主文件。这允许训练和推断脚本通过名称找到任务。此文件的内容应该是如下所示: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
是实现任务逻辑和环境的任务配置类的主要python脚本。
任务逻辑#
在Isaac Lab中,不再需要 post_reset
API。以前在 post_reset
中完成的所有操作都可以在执行基类的 __init__
方法后在 __init__
方法中完成。此时,模拟已经开始。
在OmniIsaacGymEnvs中,由于GPU API的限制,不能根据当前步骤的状态进行重置。所以,重置必须在下一个时间步骤的开始时执行。在Isaac Lab中,这个限制被消除了,因此这些任务可以按照正确的工作流程进行操作,包括应用动作、模拟步进、收集状态、计算完成标志、计算奖励、执行重置,最后计算观察。这个工作流程可以被框架自动执行,因此任务不需要 post_physics_step
API。但是,各个任务可以覆盖 step()
API以控制工作流程。
在 Isaac Lab 中,我们还将 pre_physics_step
API 与 apply_action
API 分开,用于处理来自策略的动作,后者将动作设置到仿真中。这样可以更灵活地控制在使用 decimation
时何时将动作写入仿真。 pre_physics_step
方法将在每个步骤之前调用一次,在仿真步骤之前。 apply_actions
方法将在每个 RL 步骤中调用 decimation
次,即在每个仿真步骤调用之前各一次。
调用的顺序如下:
OmniIsaacGymEnvs |
Isaac Lab |
pre_physics_step
|-- reset_idx()
|-- apply_action
post_physics_step
|-- get_observations()
|-- calculate_metrics()
|-- is_done()
|
pre_physics_step
|-- _pre_physics_step(action)
|-- _apply_action()
post_physics_step
|-- _get_dones()
|-- _get_rewards()
|-- _reset_idx()
|-- _get_observations()
|
通过这种方法,重置是基于当前步骤的动作而不是上一个步骤执行的。观察还将根据重置后的正确状态进行计算。
我们还进行了一些API的重命名:
set_up_scene(self, scene)
–>_setup_scene(self)
post_reset(self)
–>__init__(...)
pre_physics_step(self, actions)
–>_pre_physics_step(self, actions)
和_apply_action(self)
reset_idx(self, env_ids)
–>_reset_idx(self, env_ids)
get_observations(self)
–>_get_observations(self)
-_get_observations()
现在应该返回一个字典{"policy": obs}
calculate_metrics(self)
–>_get_rewards(self)
-_get_rewards()
现在应该返回奖励缓冲区is_done(self)
–>_get_dones(self)
- 现在_get_dones()
应该返回 2 个缓冲区:reset
和time_out
缓冲区
将所有内容组合在一起#
Cartpole 环境在这里被完整展示,以充分展示 OmniIsaacGymEnvs 实现和 Isaac Lab 实现之间的比较。
任务配置#
在 Isaac Lab 中,任务配置可以分为主要任务配置类和用于actor的单独配置对象。
OmniIsaacGymEnvs |
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
controlFrequencyInv: 2 # 60 Hz
sim:
dt: 0.0083 # 1/120 s
use_gpu_pipeline: ${eq:${...pipeline},"gpu"}
gravity: [0.0, 0.0, -9.81]
add_ground_plane: True
add_distant_light: False
use_fabric: True
enable_scene_query_support: False
disable_contact_processing: False
enable_cameras: False
default_physics_material:
static_friction: 1.0
dynamic_friction: 1.0
restitution: 0.0
physx:
worker_thread_count: ${....num_threads}
solver_type: ${....solver_type}
use_gpu: ${eq:${....sim_device},"gpu"} # set to False to...
solver_position_iteration_count: 4
solver_velocity_iteration_count: 0
contact_offset: 0.02
rest_offset: 0.001
bounce_threshold_velocity: 0.2
friction_offset_threshold: 0.04
friction_correlation_distance: 0.025
enable_sleeping: True
enable_stabilization: True
max_depenetration_velocity: 100.0
# GPU buffers
gpu_max_rigid_contact_count: 524288
gpu_max_rigid_patch_count: 81920
gpu_found_lost_pairs_capacity: 1024
gpu_found_lost_aggregate_pairs_capacity: 262144
gpu_total_aggregate_pairs_capacity: 1024
gpu_max_soft_body_contacts: 1048576
gpu_max_particle_contacts: 1048576
gpu_heap_capacity: 67108864
gpu_temp_buffer_capacity: 16777216
gpu_max_num_partitions: 8
Cartpole:
override_usd_defaults: False
enable_self_collisions: False
enable_gyroscopic_forces: True
solver_position_iteration_count: 4
solver_velocity_iteration_count: 0
sleep_threshold: 0.005
stabilization_threshold: 0.001
density: -1
max_depenetration_velocity: 100.0
contact_offset: 0.02
rest_offset: 0.001
|
@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
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
),
},
)
|
任务设置#
在Isaac Lab中,不再需要 post_reset
API。以前在 post_reset
中完成的所有操作都可以在执行基类的 __init__
方法后在 __init__
方法中完成。此时,模拟已经开始。
OmniIsaacGymEnvs |
Isaac Lab |
class CartpoleTask(RLTask):
def __init__(self, name, sim_config, env, offset=None) -> None:
self.update_config(sim_config)
self._max_episode_length = 500
self._num_observations = 4
self._num_actions = 1
RLTask.__init__(self, name, env)
def update_config(self, sim_config):
self._sim_config = sim_config
self._cfg = sim_config.config
self._task_cfg = sim_config.
task_config
self._num_envs = self._task_cfg["env"]["numEnvs"]
self._env_spacing = self._task_cfg["env"]["envSpacing"]
self._cartpole_positions = torch.tensor([0.0, 0.0, 2.0])
self._reset_dist = self._task_cfg["env"]["resetDist"]
self._max_push_effort = self._task_cfg["env"]["maxEffort"]
def post_reset(self):
self._cart_dof_idx = self._cartpoles.get_dof_index(
"cartJoint")
self._pole_dof_idx = self._cartpoles.get_dof_index(
"poleJoint")
# randomize all envs
indices = torch.arange(
self._cartpoles.count, dtype=torch.int64,
device=self._device)
self.reset_idx(indices)
|
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
|
场景设置#
在 OmniIsaacGymEnvs 中, set_up_scene
方法已被 Isaac Lab 中任务类中的 _setup_scene
API 替换。此外,还提供了场景克隆和碰撞过滤作为任务类在必要时调用的 API。同样,添加地平面和光源也应在任务类中处理。向场景添加actor已被替换为 self.scene.articulations["cartpole"] = self.cartpole
。
OmniIsaacGymEnvs |
Isaac Lab |
def set_up_scene(self, scene) -> None:
self.get_cartpole()
super().set_up_scene(scene)
self._cartpoles = ArticulationView(
prim_paths_expr="/World/envs/.*/Cartpole",
name="cartpole_view",
reset_xform_properties=False
)
scene.add(self._cartpoles)
return
def get_cartpole(self):
cartpole = Cartpole(
prim_path=self.default_zero_env_path+"/Cartpole",
name="Cartpole",
translation=self._cartpole_positions
)
# applies articulation settings from the
# task configuration yaml file
self._sim_config.apply_articulation_settings(
"Cartpole", get_prim_at_path(cartpole.prim_path),
self._sim_config.parse_actor_config("Cartpole")
)
|
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)
|
预物理步骤#
请注意,重置不再在 pre_physics_step
API 中执行。此外, _pre_physics_step
和 _apply_action
方法的分离允许更灵活地处理动作缓冲区并将动作设置到仿真中。
OmniIsaacGymEnvs |
IsaacLab |
def pre_physics_step(self, actions) -> None:
if not self.world.is_playing():
return
reset_env_ids = self.reset_buf.nonzero(
as_tuple=False).squeeze(-1)
if len(reset_env_ids) > 0:
self.reset_idx(reset_env_ids)
actions = actions.to(self._device)
forces = torch.zeros((self._cartpoles.count,
self._cartpoles.num_dof),
dtype=torch.float32, device=self._device)
forces[:, self._cart_dof_idx] =
self._max_push_effort * actions[:, 0]
indices = torch.arange(self._cartpoles.count,
dtype=torch.int32, device=self._device)
self._cartpoles.set_joint_efforts(
forces, indices=indices)
|
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)
|
完成和重置#
在 Isaac Lab 中, dones
是在 _get_dones()
方法中计算的,应该返回两个变量:resets
和 time_out
。 _reset_idx()
方法也在仿真后而不是在 OmniIsaacGymEnvs 中之前调用。 progress_buf
张量在 Isaac Lab 中已更名为 episode_length_buf
,框架现在会自动处理记录。任务实现不再需要递增或重置 episode_length_buf
。
OmniIsaacGymEnvs |
Isaac Lab |
def is_done(self) -> None:
resets = torch.where(
torch.abs(self.cart_pos) > self._reset_dist, 1, 0)
resets = torch.where(
torch.abs(self.pole_pos) > math.pi / 2, 1, resets)
resets = torch.where(
self.progress_buf >= self._max_episode_length, 1, resets)
self.reset_buf[:] = resets
def reset_idx(self, env_ids):
num_resets = len(env_ids)
# randomize DOF positions
dof_pos = torch.zeros((num_resets, self._cartpoles.num_dof),
device=self._device)
dof_pos[:, self._cart_dof_idx] = 1.0 * (
1.0 - 2.0 * torch.rand(num_resets, device=self._device))
dof_pos[:, self._pole_dof_idx] = 0.125 * math.pi * (
1.0 - 2.0 * torch.rand(num_resets, device=self._device))
# randomize DOF velocities
dof_vel = torch.zeros((num_resets, self._cartpoles.num_dof),
device=self._device)
dof_vel[:, self._cart_dof_idx] = 0.5 * (
1.0 - 2.0 * torch.rand(num_resets, device=self._device))
dof_vel[:, self._pole_dof_idx] = 0.25 * math.pi * (
1.0 - 2.0 * torch.rand(num_resets, device=self._device))
# apply resets
indices = env_ids.to(dtype=torch.int32)
self._cartpoles.set_joint_positions(dof_pos, indices=indices)
self._cartpoles.set_joint_velocities(dof_vel, indices=indices)
# bookkeeping
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.joint_vel[env_ids] = joint_vel
self.cartpole.write_root_pose_to_sim(
default_root_state[:, :7], env_ids)
self.cartpole.write_root_velocity_to_sim(
default_root_state[:, 7:], env_ids)
self.cartpole.write_joint_state_to_sim(
joint_pos, joint_vel, None, env_ids)
|
奖励#
在 Isaac Lab 中,奖励是在 _get_rewards
API 中实现的,应该返回奖励缓冲区,而不是直接赋值给 self.rew_buf
。还可以使用 PyTorch jit 通过定义带有 @torch.jit.script
注释的函数来执行奖励函数的计算。
OmniIsaacGymEnvs |
Isaac Lab |
def calculate_metrics(self) -> None:
reward = (1.0 - self.pole_pos * self.pole_pos
- 0.01 * torch.abs(self.cart_vel) - 0.005
* torch.abs(self.pole_vel))
reward = torch.where(
torch.abs(self.cart_pos) > self._reset_dist,
torch.ones_like(reward) * -2.0, reward)
reward = torch.where(
torch.abs(self.pole_pos) > np.pi / 2,
torch.ones_like(reward) * -2.0, reward)
self.rew_buf[:] = reward
|
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
|
观察#
在 Isaac Lab 中, _get_observations()
API 必须返回一个带有键 policy
且值为观察缓冲区的字典。当使用非对称的actor-critic状态时,评论者的状态应该有键 critic
,并与观察缓冲区一起返回在同一个字典中。
OmniIsaacGymEnvs |
Isaac Lab |
def get_observations(self) -> dict:
dof_pos = self._cartpoles.get_joint_positions(clone=False)
dof_vel = self._cartpoles.get_joint_velocities(clone=False)
self.cart_pos = dof_pos[:, self._cart_dof_idx]
self.cart_vel = dof_vel[:, self._cart_dof_idx]
self.pole_pos = dof_pos[:, self._pole_dof_idx]
self.pole_vel = dof_vel[:, self._pole_dof_idx]
self.obs_buf[:, 0] = self.cart_pos
self.obs_buf[:, 1] = self.cart_vel
self.obs_buf[:, 2] = self.pole_pos
self.obs_buf[:, 3] = self.pole_vel
observations = {self._cartpoles.name:
{"obs_buf": self.obs_buf}}
return observations
|
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
|
域随机化#
在 OmniIsaacGymEnvs 中,域随机化是通过任务的 .yaml
配置文件指定的。而在 Isaac Lab 中,域随机化配置使用 configclass
模块来指定一个由 EventTermCfg
变量组成的配置类。
以下是一个域随机化的配置类示例:
@configclass
class EventCfg:
robot_physics_material = EventTerm(
func=mdp.randomize_rigid_body_material,
mode="reset",
params={
"asset_cfg": SceneEntityCfg("robot", body_names=".*"),
"static_friction_range": (0.7, 1.3),
"dynamic_friction_range": (1.0, 1.0),
"restitution_range": (1.0, 1.0),
"num_buckets": 250,
},
)
robot_joint_stiffness_and_damping = EventTerm(
func=mdp.randomize_actuator_gains,
mode="reset",
params={
"asset_cfg": SceneEntityCfg("robot", joint_names=".*"),
"stiffness_distribution_params": (0.75, 1.5),
"damping_distribution_params": (0.3, 3.0),
"operation": "scale",
"distribution": "log_uniform",
},
)
reset_gravity = EventTerm(
func=mdp.randomize_physics_scene_gravity,
mode="interval",
is_global_time=True,
interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt)
params={
"gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]),
"operation": "add",
"distribution": "gaussian",
},
)
每个 EventTerm
对象都是 EventTermCfg
类的实例,并使用 func
参数来指定在随机化期间调用的函数,使用 mode
参数来指定 startup
、reset
或 interval
。 params
字典应提供给函数的必要参数,这些函数作为 EventTerm
的 func
参数在 events
模块中找到。
请注意,在 "asset_cfg": SceneEntityCfg("robot", body_names=".*")
参数中,提供了actor "robot"
的名称,以及作为正则表达式表达的主体或关节名称,这些将是将随机化应用于的actor和主体/关节。
与 OmniIsaacGymEnvs 的一个区别是,现在 interval
随机化是以秒而不是步数指定的。当 mode="interval"
时,还必须提供 interval_range_s
参数,该参数指定应用随机化的秒数范围。然后将随机选择此范围,以确定下一个术语的特定时间。要将步长转换为秒,请使用等式 time_s = num_steps * (decimation * dt)
。
与 OmniIsaacGymEnvs 类似,随机化 API 可用于随机化关节刚度和阻尼、关节限制、刚体材料、固定肌腱属性以及质量和刚体材料等刚体属性。物理场景重力的随机化也受支持。请注意,Isaac Lab 目前不支持scale的随机化。要随机化scale,请以每种环境以不同scale容纳actor的方式设置场景。
一旦设置了随机化术语的 configclass
类,该类必须添加到任务的基本配置类中,并分配给变量 events
。
@configclass
class MyTaskConfig:
events: EventCfg = EventCfg()
动作和观察噪声#
使用 configclass
模块也可以添加动作和观察噪声。必须将动作和观察噪声配置添加到主要任务配置中,使用 action_noise_model
和 observation_noise_model
变量:
@configclass
class MyTaskConfig:
# at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset
action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg(
noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"),
bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"),
)
# at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset
observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg(
noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"),
bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"),
)
NoiseModelWithAdditiveBiasCfg
可以用于每个步骤产生不相关的噪声,以及在重置时重新采样的相关噪声。 noise_cfg
术语指定将在每个步骤为所有环境采样的高斯分布。此噪声将在每一步添加到相应的动作和观察缓冲区中。 bias_noise_cfg
术语指定在被重置的环境的重置时间采样的相关噪声的高斯分布。相同的噪声将在剩余的剧集每一步应用,并在下一次重置时重新采样。
这取代了 OmniIsaacGymEnvs 中的以下设置:
domain_randomization:
randomize: True
randomization_params:
observations:
on_reset:
operation: "additive"
distribution: "gaussian"
distribution_parameters: [0, .0001]
on_interval:
frequency_interval: 1
operation: "additive"
distribution: "gaussian"
distribution_parameters: [0, .002]
actions:
on_reset:
operation: "additive"
distribution: "gaussian"
distribution_parameters: [0, 0.015]
on_interval:
frequency_interval: 1
operation: "additive"
distribution: "gaussian"
distribution_parameters: [0., 0.05]
启动训练#
要在 Isaac Lab 中启动训练,使用该命令:
python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless
启动推理#
要在 Isaac Lab 中启动推理,使用该命令:
python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint=<path/to/checkpoint>