File size: 7,304 Bytes
0037d53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Copyright (c) 2024-2026, The UW Lab Project Developers. (https://github.com/uw-lab/UWLab/blob/main/CONTRIBUTORS.md).
# All Rights Reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Evaluate checkpoint robustness under action noise for distillation selection.

All policies run simultaneously in the same env, each controlling a disjoint
slice of environments. This ensures identical resets/randomization and makes
results independent of checkpoint ordering.
"""

"""Launch Isaac Sim Simulator first."""

import argparse
import os
import sys

sys.path.append(
    os.path.join(os.path.dirname(__file__), "..", "..", "..", "scripts", "reinforcement_learning", "rsl_rl")
)

from isaaclab.app import AppLauncher

import cli_args  # isort: skip

parser = argparse.ArgumentParser(description="Evaluate checkpoints for distillation robustness.")
parser.add_argument("--task", type=str, required=True, help="Name of the task.")
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate (split evenly).")
parser.add_argument("--checkpoints", nargs="+", required=True, help="List of checkpoint paths to evaluate.")
parser.add_argument("--eval_steps", type=int, default=1000, help="Number of env steps to run.")
parser.add_argument("--action_noise", type=float, default=2.0, help="Std of Gaussian noise added to actions.")
parser.add_argument(
    "--agent", type=str, default="rsl_rl_cfg_entry_point", help="Name of the RL agent configuration entry point."
)
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument(
    "--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
cli_args.add_rsl_rl_args(parser)
AppLauncher.add_app_launcher_args(parser)
args_cli, hydra_args = parser.parse_known_args()

sys.argv = [sys.argv[0]] + hydra_args

app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app

"""Rest everything follows."""

import gymnasium as gym
import torch

import isaaclab_tasks  # noqa: F401
from isaaclab.envs import (
    DirectMARLEnv,
    DirectMARLEnvCfg,
    DirectRLEnvCfg,
    ManagerBasedRLEnvCfg,
    multi_agent_to_single_agent,
)
from isaaclab.managers import TerminationTermCfg as DoneTerm
from isaaclab.utils.assets import retrieve_file_path
from isaaclab_rl.rsl_rl import RslRlBaseRunnerCfg, RslRlVecEnvWrapper
from rsl_rl.runners import DistillationRunner, OnPolicyRunner

import uwlab_tasks  # noqa: F401
from uwlab_tasks.manager_based.manipulation.omnireset import mdp as task_mdp
from uwlab_tasks.utils.hydra import hydra_task_config


@hydra_task_config(args_cli.task, args_cli.agent)
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlBaseRunnerCfg):
    """Evaluate checkpoints under action noise and rank by success throughput."""
    agent_cfg: RslRlBaseRunnerCfg = cli_args.update_rsl_rl_cfg(agent_cfg, args_cli)
    agent_cfg = cli_args.sanitize_rsl_rl_cfg(agent_cfg)
    env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs
    env_cfg.seed = agent_cfg.seed
    env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device

    num_policies = len(args_cli.checkpoints)
    num_envs = env_cfg.scene.num_envs
    envs_per_policy = num_envs // num_policies
    slices = []
    for i in range(num_policies):
        start = i * envs_per_policy
        end = (i + 1) * envs_per_policy if i < num_policies - 1 else num_envs
        slices.append((start, end))

    env_cfg.terminations.success = DoneTerm(
        func=task_mdp.consecutive_success_state_with_min_length,
        params={"num_consecutive_successes": 5, "min_episode_length": 10},
    )

    env = gym.make(args_cli.task, cfg=env_cfg)
    if isinstance(env.unwrapped, DirectMARLEnv):
        env = multi_agent_to_single_agent(env)
    env = RslRlVecEnvWrapper(env, clip_actions=agent_cfg.clip_actions)

    term_names = env.unwrapped.termination_manager._term_names
    assert "success" in term_names, f"'success' not in termination terms: {term_names}"
    success_idx = term_names.index("success")

    policies = []
    policy_nns = []
    for ckpt_path in args_cli.checkpoints:
        resume_path = retrieve_file_path(ckpt_path)
        if agent_cfg.class_name == "OnPolicyRunner":
            runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
        elif agent_cfg.class_name == "DistillationRunner":
            runner = DistillationRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
        else:
            raise ValueError(f"Unsupported runner class: {agent_cfg.class_name}")
        runner.load(resume_path)
        policies.append(runner.get_inference_policy(device=env.unwrapped.device))
        try:
            policy_nns.append(runner.alg.policy)
        except AttributeError:
            policy_nns.append(runner.alg.actor_critic)

    print(f"\n{'=' * 60}")
    print(f"Running {num_policies} policies across {num_envs} envs")
    print(f"Action noise std: {args_cli.action_noise}")
    print(f"Eval steps: {args_cli.eval_steps}")
    for i, ckpt in enumerate(args_cli.checkpoints):
        s, e = slices[i]
        print(f"  Policy {i}: envs [{s}:{e}] ({e - s} envs) <- {os.path.basename(ckpt)}")
    print(f"{'=' * 60}")

    total_successes = [0] * num_policies
    total_episodes = [0] * num_policies

    obs = env.get_observations()

    for step in range(args_cli.eval_steps):
        with torch.inference_mode():
            action_slices = []
            for i, policy in enumerate(policies):
                s, e = slices[i]
                action_slices.append(policy(obs)[s:e])
            actions = torch.cat(action_slices, dim=0)
            actions = actions + args_cli.action_noise * torch.randn_like(actions)
            obs, _, dones, extras = env.step(actions)
            for pnn in policy_nns:
                pnn.reset(dones)

        if dones.any():
            reset_ids = (dones > 0).nonzero(as_tuple=False).reshape(-1)
            term_dones = env.unwrapped.termination_manager._term_dones[reset_ids]

            for env_id, term_row in zip(reset_ids, term_dones):
                eid = env_id.item()
                pidx = next(i for i, (s, e) in enumerate(slices) if s <= eid < e)
                total_episodes[pidx] += 1
                active = term_row.nonzero(as_tuple=False).flatten().cpu().tolist()
                if success_idx in active:
                    total_successes[pidx] += 1

    print(f"\n{'=' * 60}")
    print(f"RANKING BY THROUGHPUT (action_noise={args_cli.action_noise}, steps={args_cli.eval_steps})")
    print(f"{'=' * 60}")
    ranking = []
    for i, ckpt in enumerate(args_cli.checkpoints):
        rate = total_successes[i] / total_episodes[i] if total_episodes[i] > 0 else 0.0
        ranking.append((ckpt, total_successes[i], total_episodes[i], rate))
    ranking.sort(key=lambda x: x[1], reverse=True)
    for rank, (ckpt, succ, eps, rate) in enumerate(ranking, 1):
        print(f"  #{rank}: {os.path.basename(ckpt)}")
        print(f"       successes={succ}  episodes={eps}  rate={rate:.1%}")

    env.close()


if __name__ == "__main__":
    main()
    simulation_app.close()