diff --git a/docs/source/features/hydra.rst b/docs/source/features/hydra.rst index 47e84fb328c..c002199a5bc 100644 --- a/docs/source/features/hydra.rst +++ b/docs/source/features/hydra.rst @@ -127,3 +127,137 @@ the post init update is as follows: Here, when modifying ``env.decimation`` or ``env.sim.dt``, the user needs to give the updated ``env.sim.render_interval``, ``env.scene.height_scanner.update_period``, and ``env.scene.contact_forces.update_period`` as input as well. + + +Group Override +-------------- +Group override lets you swap out entire groups of environment- or agent-level settings in one go. +Instead of overriding individual fields, you select a named preset defined in your code. + + +Group Presets +^^^^^^^^^^^^^ +First define the available group override options + + +.. code-block:: python + + @configclass + class StateNoNoiseObservationsCfg: + """Observation specifications for the MDP.""" + + @configclass + class PolicyCfg(ObsGroup): + """Observations for policy group.""" + + # observation terms (order preserved) + joint_pos = ObsTerm(func=mdp.joint_pos_rel) + # other terms ....... + + def __post_init__(self): + self.enable_corruption = False + self.concatenate_terms = True + + # observation groups + policy: PolicyCfg = PolicyCfg() + + + @configclass + class EnvConfigurables: + env: dict[str, any] = { + "observations": { + "state_obs_no_noise": StateNoNoiseObservationsCfg(), + "state_obs_noisy": # other option, + }, + "actions.arm_action": { + "joint_pos_arm_action": mdp.JointPositionActionCfg( + asset_name="robot", joint_names=["panda_joint.*"], scale=0.5, use_default_offset=True + ), + "osc_arm_action": mdp.OperationalSpaceControllerActionCfg( + asset_name="robot", + # rest of fields + ), + }, + "events": { + "rand_joint_pos_friction": JointRandPositionFrictionEventCfg(), + "rand_joint_pos_friction_amarture": JointRandPositionFrictionAmartureEventCfg(), + }, + "events.reset_robot_joints": { + "aggressive": EventTerm( + func=mdp.reset_joints_by_scale, + mode="reset", + params={ + "position_range": (0.0, 2.0), + "velocity_range": (0.0, 1.0), + }, + ), + "easy": # easy EventTerm with narrower ranges + }, + } + + + + @configclass + class AgentConfigurables(EnvConfigurables): + agent: dict[str, any] = { + "policy": { + "large_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[512, 256, 128, 64], + critic_hidden_dims=[512, 256, 128, 64], + activation="elu", + ), + "medium_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[256, 128, 64], + critic_hidden_dims=[256, 128, 64], + activation="elu", + ), + "small_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[128, 64], + critic_hidden_dims=[128, 64], + activation="elu", + ), + }, + # algorithm cfg..... + } + + +Group Registration +^^^^^^^^^^^^^^^^^^ +When you register your Gym environment, provide the ``configurable_entry_point`` pointing to your ``@configclass``: + +.. code-block:: python + + gym.register( + id="Isaac-Reach-Franka-v0", + entry_point="isaaclab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + # … other cfg entry points … + "configurable_entry_point": f"{agents.__name__}.configurables:AgentConfigurables" + }, + ) + + +Override Syntax +^^^^^^^^^^^^^^^ +Select one preset per group via Hydra-style CLI flags. For example:: + + python scripts/reinforcement_learning/rsl_rl/train.py \ + --task=Isaac-Reach-Franka-v0 \ + --headless \ + env.events=rand_joint_pos_friction_amarture \ + env.observations=state_obs_no_noise \ + env.actions.arm_action=osc_arm_action \ + agent.policy=large_network + +Under the hood, Hydra will replace: + +- ``env.events`` with ``EnvConfigurables.env["rand_joint_pos_friction_amarture"]`` +- ``env.observations`` with ``EnvConfigurables.env["state_obs_no_noise"]`` +- ``env.actions.arm_action`` with ``EnvConfigurables.env["actions.arm_action"]["osc_arm_action"]`` +- ``agent.policy`` with ``AgentConfigurables.agent["large_network"]`` + +allowing you to switch qualitative modes of your experiments with a single flag. diff --git a/source/isaaclab_tasks/config/extension.toml b/source/isaaclab_tasks/config/extension.toml index d827c066495..1a6d1b88d07 100644 --- a/source/isaaclab_tasks/config/extension.toml +++ b/source/isaaclab_tasks/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.10.36" +version = "0.11.0" # Description title = "Isaac Lab Environments" diff --git a/source/isaaclab_tasks/docs/CHANGELOG.rst b/source/isaaclab_tasks/docs/CHANGELOG.rst index d5f59b1b7ac..42c7a8ae059 100644 --- a/source/isaaclab_tasks/docs/CHANGELOG.rst +++ b/source/isaaclab_tasks/docs/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog --------- +0.11.0 (2025-07-05) +~~~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Add new feature that support hydra group config override, and provide example at Isaac-Reach-Franka-v0 env + + 0.10.36 (2025-06-26) ~~~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/__init__.py index 8c159b81eb0..50a1e7f8d40 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/__init__.py @@ -24,6 +24,7 @@ "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:FrankaReachPPORunnerCfg", "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + "configurable_entry_point": f"{agents.__name__}.configurables:AgentConfigurables", }, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/agents/configurables.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/agents/configurables.py new file mode 100644 index 00000000000..e297af31215 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/agents/configurables.py @@ -0,0 +1,66 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from isaaclab.utils import configclass + +from isaaclab_rl.rsl_rl import RslRlPpoActorCriticCfg, RslRlPpoAlgorithmCfg + +from ..configurables import EnvConfigurables + + +@configclass +class AgentConfigurables(EnvConfigurables): + agent: dict[str, any] = { + "policy": { + "large_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[512, 256, 128, 64], + critic_hidden_dims=[512, 256, 128, 64], + activation="elu", + ), + "medium_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[256, 128, 64], + critic_hidden_dims=[256, 128, 64], + activation="elu", + ), + "small_network": RslRlPpoActorCriticCfg( + init_noise_std=1.0, + actor_hidden_dims=[128, 64], + critic_hidden_dims=[128, 64], + activation="elu", + ), + }, + "algorithm": { + "standard": RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.001, + num_learning_epochs=8, + num_mini_batches=4, + learning_rate=1.0e-3, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ), + "small_batch": RslRlPpoAlgorithmCfg( + value_loss_coef=1.0, + use_clipped_value_loss=True, + clip_param=0.2, + entropy_coef=0.001, + num_learning_epochs=8, + num_mini_batches=16, + learning_rate=1.0e-4, + schedule="adaptive", + gamma=0.99, + lam=0.95, + desired_kl=0.01, + max_grad_norm=1.0, + ), + }, + } diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/configurables.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/configurables.py new file mode 100644 index 00000000000..60726e3c31e --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/configurables.py @@ -0,0 +1,169 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +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 SceneEntityCfg +from isaaclab.utils import configclass +from isaaclab.utils.noise import AdditiveUniformNoiseCfg as Unoise + +import isaaclab_tasks.manager_based.manipulation.reach.mdp as mdp + +from ...reach_env_cfg import EventCfg + + +# Observation configurations +@configclass +class StateNoNoiseObservationsCfg: + """Observation specifications for the MDP.""" + + @configclass + class PolicyCfg(ObsGroup): + """Observations for policy group.""" + + # observation terms (order preserved) + joint_pos = ObsTerm(func=mdp.joint_pos_rel) + joint_vel = ObsTerm(func=mdp.joint_vel_rel) + pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "ee_pose"}) + actions = ObsTerm(func=mdp.last_action) + + def __post_init__(self): + self.enable_corruption = False + self.concatenate_terms = True + + # observation groups + policy: PolicyCfg = PolicyCfg() + + +@configclass +class StateNoisyObservationsCfg: + """Observation specifications for the MDP.""" + + @configclass + class PolicyCfg(ObsGroup): + """Observations for policy group.""" + + # observation terms (order preserved) + joint_pos = ObsTerm(func=mdp.joint_pos_rel, noise=Unoise(n_min=-0.1, n_max=0.1)) + joint_vel = ObsTerm(func=mdp.joint_vel_rel, noise=Unoise(n_min=-0.1, n_max=0.1)) + pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "ee_pose"}) + actions = ObsTerm(func=mdp.last_action) + + def __post_init__(self): + self.enable_corruption = True + self.concatenate_terms = True + + # observation groups + policy: PolicyCfg = PolicyCfg() + + +@configclass +class JointRandPositionFrictionEventCfg(EventCfg): + + reset_robot_joint_friction = EventTerm( + func=mdp.randomize_joint_parameters, + min_step_count_between_reset=720, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "friction_distribution_params": (0.9, 1.1), + "operation": "scale", + "distribution": "gaussian", + }, + ) + + +@configclass +class JointRandPositionFrictionAmartureEventCfg(JointRandPositionFrictionEventCfg): + """Configuration for events.""" + + reset_robot_joint_amature = EventTerm( + func=mdp.randomize_joint_parameters, + min_step_count_between_reset=720, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "armature_distribution_params": (0.9, 1.1), + "operation": "scale", + "distribution": "gaussian", + }, + ) + + +@configclass +class EnvConfigurables: + env: dict[str, any] = { + "observations": { + "state_obs_no_noise": StateNoNoiseObservationsCfg(), + "state_obs_noisy": StateNoisyObservationsCfg(), + }, + "actions.arm_action": { + "ik_abs_arm_action": mdp.DifferentialInverseKinematicsActionCfg( + asset_name="robot", + joint_names=["panda_joint.*"], + body_name="panda_hand", + controller=mdp.DifferentialIKControllerCfg( + command_type="pose", use_relative_mode=False, ik_method="dls" + ), + body_offset=mdp.DifferentialInverseKinematicsActionCfg.OffsetCfg(pos=[0.0, 0.0, 0.107]), + ), + "ik_rel_arm_action": mdp.DifferentialInverseKinematicsActionCfg( + asset_name="robot", + joint_names=["panda_joint.*"], + body_name="panda_hand", + controller=mdp.DifferentialIKControllerCfg( + command_type="pose", use_relative_mode=True, ik_method="dls" + ), + scale=0.5, + body_offset=mdp.DifferentialInverseKinematicsActionCfg.OffsetCfg(pos=[0.0, 0.0, 0.107]), + ), + "joint_pos_arm_action": mdp.JointPositionActionCfg( + asset_name="robot", joint_names=["panda_joint.*"], scale=0.5, use_default_offset=True + ), + "osc_arm_action": mdp.OperationalSpaceControllerActionCfg( + asset_name="robot", + joint_names=["panda_joint.*"], + body_name="panda_hand", + controller_cfg=mdp.OperationalSpaceControllerCfg( + target_types=["pose_abs"], + impedance_mode="variable_kp", + inertial_dynamics_decoupling=True, + partial_inertial_dynamics_decoupling=False, + gravity_compensation=False, + motion_stiffness_task=100.0, + motion_damping_ratio_task=1.0, + motion_stiffness_limits_task=(50.0, 200.0), + nullspace_control="position", + ), + nullspace_joint_pos_target="center", + position_scale=1.0, + orientation_scale=1.0, + stiffness_scale=100.0, + ), + }, + "events": { + "rand_joint_pos_friction": JointRandPositionFrictionEventCfg(), + "rand_joint_pos_friction_amarture": JointRandPositionFrictionAmartureEventCfg(), + }, + "events.reset_robot_joints": { + "aggressive": EventTerm( + func=mdp.reset_joints_by_scale, + mode="reset", + params={ + "position_range": (0.0, 2.0), + "velocity_range": (0.0, 1.0), + }, + ), + "easy": EventTerm( + func=mdp.reset_joints_by_scale, + mode="reset", + params={ + "position_range": (0.0, 0.5), + "velocity_range": (0.0, 0.0), + }, + ), + }, + } diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py b/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py index 6e2648aa029..34e293dfad0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py @@ -7,11 +7,13 @@ import functools -from collections.abc import Callable +import gymnasium as gym +from collections.abc import Callable, Mapping try: import hydra from hydra.core.config_store import ConfigStore + from hydra.core.hydra_config import HydraConfig from omegaconf import DictConfig, OmegaConf except ImportError: raise ImportError("Hydra is not installed. Please install it by running 'pip install hydra-core'.") @@ -55,9 +57,31 @@ def register_task_to_hydra( cfg_dict = {"env": env_cfg_dict, "agent": agent_cfg_dict} # replace slices with strings because OmegaConf does not support slices cfg_dict = replace_slices_with_strings(cfg_dict) - # store the configuration to Hydra - ConfigStore.instance().store(name=task_name, node=cfg_dict) - return env_cfg, agent_cfg + config_store = ConfigStore.instance() + + has_hydra_group_configuration = "configurable_entry_point" in gym.spec(task_name).kwargs + if has_hydra_group_configuration: + hydra_groups_cfg = load_cfg_from_registry(task_name, "configurable_entry_point") + hydra_groups_cfg = replace_env_cfg_spaces_with_strings(hydra_groups_cfg) + configurables_dict = hydra_groups_cfg.to_dict() + configurables_dict = replace_slices_with_strings(configurables_dict) + default_groups = [] + for root_config_name, root_config_dict in configurables_dict.items(): + for group_name, group_option_dict in root_config_dict.items(): + group_path = f"{root_config_name}.{group_name}" + default_groups.append(group_path) + config_store.store(group=group_path, name="default", node=getattr_nested(cfg_dict, group_path)) + for option_name, option_val in group_option_dict.items(): + config_store.store(group=group_path, name=option_name, node=option_val) + + root_defaults = ["_self_"] + [{grp: "default"} for grp in default_groups] + cfg_dict = {"defaults": root_defaults, **cfg_dict} + else: + # no predefined-group cfg override, only field overrides + hydra_groups_cfg = None + config_store.store(name=task_name, node=OmegaConf.create(cfg_dict), group=None) + + return env_cfg, agent_cfg, hydra_groups_cfg def hydra_task_config(task_name: str, agent_cfg_entry_point: str) -> Callable: @@ -78,15 +102,29 @@ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # register the task to Hydra - env_cfg, agent_cfg = register_task_to_hydra(task_name.split(":")[-1], agent_cfg_entry_point) + env_cfg, agent_cfg, configurables = register_task_to_hydra(task_name.split(":")[-1], agent_cfg_entry_point) # define the new Hydra main function @hydra.main(config_path=None, config_name=task_name.split(":")[-1], version_base="1.3") - def hydra_main(hydra_env_cfg: DictConfig, env_cfg=env_cfg, agent_cfg=agent_cfg): + def hydra_main( + hydra_env_cfg: DictConfig, env_cfg=env_cfg, agent_cfg=agent_cfg, configurables=configurables + ): # convert to a native dictionary hydra_env_cfg = OmegaConf.to_container(hydra_env_cfg, resolve=True) # replace string with slices because OmegaConf does not support slices hydra_env_cfg = replace_strings_with_slices(hydra_env_cfg) + # update the group configs with Hydra command line arguments + has_hydra_group_configuration = "configurable_entry_point" in gym.spec(task_name).kwargs + if has_hydra_group_configuration: + hydra_cfg = HydraConfig.get() + configurables = replace_strings_with_slices(configurables) + for key in configurables.env.keys(): + cmd_group_choice = hydra_cfg.runtime.choices[f"env.{key}"] + if cmd_group_choice != "default": + setattr_nested(env_cfg, key, configurables.env[key][cmd_group_choice]) + setattr_nested( + hydra_env_cfg["env"], key, configurables.env[key][cmd_group_choice].to_dict() + ) # update the configs with the Hydra command line arguments env_cfg.from_dict(hydra_env_cfg["env"]) # replace strings that represent gymnasium spaces because OmegaConf does not support them. @@ -96,6 +134,11 @@ def hydra_main(hydra_env_cfg: DictConfig, env_cfg=env_cfg, agent_cfg=agent_cfg): if isinstance(agent_cfg, dict) or agent_cfg is None: agent_cfg = hydra_env_cfg["agent"] else: + if has_hydra_group_configuration: + for key in configurables.agent.keys(): + cmd_group_choice = hydra_cfg.runtime.choices[f"agent.{key}"] + if cmd_group_choice != "default": + setattr_nested(agent_cfg, key, configurables.agent[key][cmd_group_choice]) agent_cfg.from_dict(hydra_env_cfg["agent"]) # call the original function func(env_cfg, agent_cfg, *args, **kwargs) @@ -106,3 +149,27 @@ def hydra_main(hydra_env_cfg: DictConfig, env_cfg=env_cfg, agent_cfg=agent_cfg): return wrapper return decorator + + +def setattr_nested(obj: object, attr_path: str, value: object) -> None: + attrs = attr_path.split(".") + for attr in attrs[:-1]: + if isinstance(obj, Mapping): + obj = obj[attr] + else: + obj = getattr(obj, attr) + last = attrs[-1] + if isinstance(obj, Mapping): + obj[last] = value + else: + setattr(obj, last, value) + + +def getattr_nested(obj: object, attr_path: str) -> object: + attrs = attr_path.split(".") + for attr in attrs: + if isinstance(obj, Mapping): + obj = obj[attr] + else: + obj = getattr(obj, attr) + return obj diff --git a/source/isaaclab_tasks/test/test_hydra.py b/source/isaaclab_tasks/test/test_hydra.py index c3b24fcaf8d..6e5f696fcc1 100644 --- a/source/isaaclab_tasks/test/test_hydra.py +++ b/source/isaaclab_tasks/test/test_hydra.py @@ -17,41 +17,65 @@ """Rest everything follows.""" import functools +import gymnasium as gym from collections.abc import Callable import hydra from hydra import compose, initialize from omegaconf import OmegaConf +from isaaclab.envs.utils.spaces import replace_strings_with_env_cfg_spaces from isaaclab.utils import replace_strings_with_slices import isaaclab_tasks # noqa: F401 -from isaaclab_tasks.utils.hydra import register_task_to_hydra +from isaaclab_tasks.utils.hydra import register_task_to_hydra, setattr_nested def hydra_task_config_test(task_name: str, agent_cfg_entry_point: str) -> Callable: """Copied from hydra.py hydra_task_config, since hydra.main requires a single point of entry, which will not work with multiple tests. Here, we replace hydra.main with hydra initialize - and compose.""" + and compose, since without hydra.main HydraConfig.get() will error, so we replace it with another + compose with input args return_hydra_config=True.""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # register the task to Hydra - env_cfg, agent_cfg = register_task_to_hydra(task_name, agent_cfg_entry_point) - - # replace hydra.main with initialize and compose + env_cfg, agent_cfg, configurables = register_task_to_hydra(task_name.split(":")[-1], agent_cfg_entry_point) with initialize(config_path=None, version_base="1.3"): - hydra_env_cfg = compose(config_name=task_name, overrides=sys.argv[1:]) + hydra_cfg = compose( + config_name=task_name.split(":")[-1], overrides=sys.argv[1:], return_hydra_config=True + )["hydra"] + hydra_env_cfg = compose(config_name=task_name.split(":")[-1], overrides=sys.argv[1:]) # convert to a native dictionary hydra_env_cfg = OmegaConf.to_container(hydra_env_cfg, resolve=True) # replace string with slices because OmegaConf does not support slices hydra_env_cfg = replace_strings_with_slices(hydra_env_cfg) + # update the group configs with Hydra command line arguments + has_hydra_group_configuration = "configurable_entry_point" in gym.spec(task_name).kwargs + if has_hydra_group_configuration: + configurables = replace_strings_with_slices(configurables) + for key in configurables.env.keys(): + cmd_group_choice = hydra_cfg["runtime"]["choices"][f"env.{key}"] + if cmd_group_choice != "default": + setattr_nested(env_cfg, key, configurables.env[key][cmd_group_choice]) + setattr_nested( + hydra_env_cfg["env"], key, configurables.env[key][cmd_group_choice].to_dict() + ) # update the configs with the Hydra command line arguments env_cfg.from_dict(hydra_env_cfg["env"]) - if isinstance(agent_cfg, dict): + # replace strings that represent gymnasium spaces because OmegaConf does not support them. + # this must be done after converting the env configs from dictionary to avoid internal reinterpretations + env_cfg = replace_strings_with_env_cfg_spaces(env_cfg) + # get agent configs + if isinstance(agent_cfg, dict) or agent_cfg is None: agent_cfg = hydra_env_cfg["agent"] else: + if has_hydra_group_configuration: + for key in configurables.agent.keys(): + cmd_group_choice = hydra_cfg["runtime"]["choices"][f"agent.{key}"] + if cmd_group_choice != "default": + setattr_nested(agent_cfg, key, configurables.agent[key][cmd_group_choice]) agent_cfg.from_dict(hydra_env_cfg["agent"]) # call the original function func(env_cfg, agent_cfg, *args, **kwargs) @@ -103,3 +127,32 @@ def main(env_cfg, agent_cfg): # clean up sys.argv = [sys.argv[0]] hydra.core.global_hydra.GlobalHydra.instance().clear() + + +def test_hydra_group_override(): + """Test the hydra configuration system for group overriding behavior""" + + # set hardcoded command line arguments + sys.argv = [ + sys.argv[0], + "env.events=rand_joint_pos_friction_amarture", + "env.observations=state_obs_no_noise", + "env.actions.arm_action=osc_arm_action", + "agent.policy=large_network", + ] + + @hydra_task_config_test("Isaac-Reach-Franka-v0", "rsl_rl_cfg_entry_point") + def main(env_cfg, agent_cfg): + # env + assert hasattr(env_cfg.events, "reset_robot_joints") + assert hasattr(env_cfg.events, "reset_robot_joint_friction") + assert hasattr(env_cfg.events, "reset_robot_joint_amature") + assert env_cfg.observations.policy.joint_pos.noise is None + assert not env_cfg.observations.policy.enable_corruption + assert type(env_cfg.actions.arm_action).__name__ == "OperationalSpaceControllerActionCfg" + assert agent_cfg.policy.actor_hidden_dims == [512, 256, 128, 64] + + main() + # clean up + sys.argv = [sys.argv[0]] + hydra.core.global_hydra.GlobalHydra.instance().clear()