[IsaacLab] 03. Task 설계 구조의 이해 part2

IsaacLab
RL
Task Design
IsaacLab 학습 프레임워크 구조 이해
Author

Jinwon Kim

Published

April 19, 2025

Introduction

본 문서에서는 direct 방식의 cartpole 환경과, manager 방식의 cartpole 환경을 비교해보고자 합니다.

각 코드는 아래에서 확인이 가능합니다.

  • /workspace/isaaclab/source/isaaclab_tasks/isaaclab_tasks/direct/cartpole/cartpole_env.py
  • /workspace/isaaclab/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py

코드 설명

Direct 환경

Direct 환경은 전통적인 환경 구현 방식과 유사하며, 하나의 클래스가 모든 구성 요소를 직접 구현합니다. 이는 구현의 투명성을 제공하며, 복잡한 로직 구현에 유리합니다. 또한, PyTorch JIT 또는 Warp와 같은 최적화 프레임워크를 활용해 성능상의 이점을 제공합니다.

코드 보기-cartpole_env.py
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

import math
import torch
from collections.abc import Sequence

from isaaclab_assets.robots.cartpole import CARTPOLE_CFG

import isaaclab.sim as sim_utils
from isaaclab.assets import Articulation, ArticulationCfg
from isaaclab.envs import DirectRLEnv, DirectRLEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg
from isaaclab.sim.spawners.from_files import GroundPlaneCfg, spawn_ground_plane
from isaaclab.utils import configclass
from isaaclab.utils.math import sample_uniform


@configclass
class CartpoleEnvCfg(DirectRLEnvCfg):
    # 환경 설정
    decimation = 2
    episode_length_s = 5.0
    action_scale = 100.0  # [N]
    action_space = 1
    observation_space = 4
    state_space = 0

    # 시뮬레이션 설정
    sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation)

    # 로봇 설정
    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: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=4.0, replicate_physics=True)

    # 리셋 설정
    max_cart_pos = 3.0  # 카트가 이 위치를 초과하면 리셋됩니다 [m]
    initial_pole_angle_range = [-0.25, 0.25]  # 리셋 시 폴 각도가 샘플링되는 범위 [rad]

    # 보상 스케일
    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


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

    def _setup_scene(self):
        self.cartpole = Articulation(self.cfg.robot_cfg)
        # 지면 추가
        spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg())
        # 복제 및 복제
        self.scene.clone_environments(copy_from_source=False)
        # 장면에 관절 추가
        self.scene.articulations["cartpole"] = self.cartpole
        # 조명 추가
        light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
        light_cfg.func("/World/Light", light_cfg)

    def _get_observations(self) -> dict:
        obs = torch.cat(
            (
                self.joint_pos[:, self._pole_dof_idx[0]].unsqueeze(dim=1),
                self.joint_vel[:, self._pole_dof_idx[0]].unsqueeze(dim=1),
                self.joint_pos[:, self._cart_dof_idx[0]].unsqueeze(dim=1),
                self.joint_vel[:, self._cart_dof_idx[0]].unsqueeze(dim=1),
            ),
            dim=-1,
        )
        observations = {"policy": obs}
        return observations

    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
구성 요소 설명
CartpoleEnvCfg Direct 환경의 설정을 정의하는 클래스입니다. 시뮬레이션의 decimation, episode 길이, 행동 스케일 등을 설정합니다.
CartpoleEnv Direct 환경을 구현하는 클래스입니다. DirectRLEnv를 상속받아 환경의 초기화, 관찰, 보상 계산 등을 수행합니다.
_setup_scene 시뮬레이션 장면을 설정하는 함수입니다. 로봇과 환경을 초기화합니다.
_get_observations 에이전트의 관찰 데이터를 수집하여 반환하는 함수입니다. 관절 위치와 속도를 포함합니다.
_get_rewards 에이전트의 보상을 계산하는 함수입니다. 보상 계산 로직은 생략되어 있습니다.

Manager-based 환경

Manager-based 환경은 작업을 개별적으로 관리되는 구성 요소들로 분해하여 모듈화된 구현을 촉진합니다. 이는 협업에 효과적이며, 다양한 구성 요소를 손쉽게 교체할 수 있어 프로토타이핑과 실험에 유리합니다.

코드 보기-cartpole_env_cfg.py
import math

import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg, AssetBaseCfg
from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import EventTermCfg as EventTerm
from isaaclab.managers import ObservationGroupCfg as ObsGroup
from isaaclab.managers import ObservationTermCfg as ObsTerm
from isaaclab.managers import RewardTermCfg as RewTerm
from isaaclab.managers import SceneEntityCfg
from isaaclab.managers import TerminationTermCfg as DoneTerm
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass

import isaaclab_tasks.manager_based.classic.cartpole.mdp as mdp

##
# Pre-defined configs
##
from isaaclab_assets.robots.cartpole import CARTPOLE_CFG  # isort:skip


##
# Scene definition
##


@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
    """Configuration for a cart-pole scene."""

    # ground plane
    ground = AssetBaseCfg(
        prim_path="/World/ground",
        spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)),
    )

    # cartpole
    robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")

    # lights
    dome_light = AssetBaseCfg(
        prim_path="/World/DomeLight",
        spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0),
    )


##
# MDP settings
##


@configclass
class ActionsCfg:
    """Action specifications for the MDP."""

    joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0)


@configclass
class ObservationsCfg:
    """Observation specifications for the MDP."""

    @configclass
    class PolicyCfg(ObsGroup):
        """Observations for policy group."""

        # observation terms (order preserved)
        joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel)
        joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel)

        def __post_init__(self) -> None:
            self.enable_corruption = False
            self.concatenate_terms = True

    # observation groups
    policy: PolicyCfg = PolicyCfg()


@configclass
class EventCfg:
    """Configuration for events."""

    # reset
    reset_cart_position = EventTerm(
        func=mdp.reset_joints_by_offset,
        mode="reset",
        params={
            "asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]),
            "position_range": (-1.0, 1.0),
            "velocity_range": (-0.5, 0.5),
        },
    )

    reset_pole_position = EventTerm(
        func=mdp.reset_joints_by_offset,
        mode="reset",
        params={
            "asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]),
            "position_range": (-0.25 * math.pi, 0.25 * math.pi),
            "velocity_range": (-0.25 * math.pi, 0.25 * math.pi),
        },
    )


@configclass
class RewardsCfg:
    """Reward terms for the MDP."""

    # (1) Constant running reward
    alive = RewTerm(func=mdp.is_alive, weight=1.0)
    # (2) Failure penalty
    terminating = RewTerm(func=mdp.is_terminated, weight=-2.0)
    # (3) Primary task: keep pole upright
    pole_pos = RewTerm(
        func=mdp.joint_pos_target_l2,
        weight=-1.0,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), "target": 0.0},
    )
    # (4) Shaping tasks: lower cart velocity
    cart_vel = RewTerm(
        func=mdp.joint_vel_l1,
        weight=-0.01,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"])},
    )
    # (5) Shaping tasks: lower pole angular velocity
    pole_vel = RewTerm(
        func=mdp.joint_vel_l1,
        weight=-0.005,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"])},
    )


@configclass
class TerminationsCfg:
    """Termination terms for the MDP."""

    # (1) Time out
    time_out = DoneTerm(func=mdp.time_out, time_out=True)
    # (2) Cart out of bounds
    cart_out_of_bounds = DoneTerm(
        func=mdp.joint_pos_out_of_manual_limit,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), "bounds": (-3.0, 3.0)},
    )


##
# Environment configuration
##


@configclass
class CartpoleEnvCfg(ManagerBasedRLEnvCfg):
    """Configuration for the cartpole environment."""

    # Scene settings
    scene: CartpoleSceneCfg = CartpoleSceneCfg(num_envs=4096, env_spacing=4.0)
    # Basic settings
    observations: ObservationsCfg = ObservationsCfg()
    actions: ActionsCfg = ActionsCfg()
    events: EventCfg = EventCfg()
    # MDP settings
    rewards: RewardsCfg = RewardsCfg()
    terminations: TerminationsCfg = TerminationsCfg()

    # Post initialization
    def __post_init__(self) -> None:
        """Post initialization."""
        # general settings
        self.decimation = 2
        self.episode_length_s = 5
        # viewer settings
        self.viewer.eye = (8.0, 0.0, 5.0)
        # simulation settings
        self.sim.dt = 1 / 120
        self.sim.render_interval = self.decimation
구성 요소 설명
CartpoleSceneCfg 카트폴 장면을 설정하는 클래스입니다. 지면, 로봇, 조명 등의 설정을 포함합니다.
ActionsCfg MDP의 행동 사양을 정의하는 클래스입니다. 로봇의 조인트에 대한 힘을 설정합니다.
ObservationsCfg MDP의 관찰 사양을 정의하는 클래스입니다. 정책 그룹에 대한 관찰을 설정합니다.
EventCfg 이벤트 구성을 정의하는 클래스입니다. 카트와 폴의 위치를 초기화하는 이벤트를 설정합니다.
RewardsCfg MDP의 보상 항목을 정의하는 클래스입니다. 다양한 보상 조건을 설정합니다.
TerminationsCfg MDP의 종료 조건을 정의하는 클래스입니다. 시간 초과 및 카트의 경계 초과 조건을 설정합니다.
CartpoleEnvCfg 카트폴 환경의 구성을 정의하는 클래스입니다. 장면, 관찰, 행동, 이벤트, 보상, 종료 조건을 포함합니다.

최종 정리

Direct vs Manager-based 비교

특징 Direct-based Manager-based
모듈화 하나의 클래스가 모든 구성 요소를 직접 구현합니다. 환경을 개별 구성 요소로 분해하여 모듈화된 구현을 촉진합니다.
구성 요소 교체 용이성 구성 요소가 하나의 클래스에 통합되어 있어 교체가 어렵습니다. 다양한 구성 요소를 손쉽게 교체할 수 있어 프로토타이핑과 실험에 유리합니다.
세밀한 제어 환경의 로직에 대해 보다 세밀한 제어가 가능합니다. 매니저를 통해 간접적으로 제어하므로 세밀한 제어가 제한적일 수 있습니다.
협업 협업 시 모든 구성 요소가 하나의 클래스에 포함되어 있어 분업이 어려울 수 있습니다. 모듈화된 구조로 인해 협업에 효과적이며, 개별 개발자가 특정 측면에 집중할 수 있습니다.
성능 최적화 PyTorch JIT 또는 Warp와 같은 최적화 프레임워크를 활용해 성능상의 이점을 제공합니다. 모듈화로 인해 성능 최적화가 제한적일 수 있습니다.
투명성 구현의 투명성을 제공합니다. 매니저에 의해 로직이 추상화되어 있어 구현의 투명성이 낮을 수 있습니다.
복잡한 로직 구현 복잡한 로직 구현에 유리합니다. 복잡한 로직을 구현하기에는 적합하지 않을 수 있습니다.

References