Download evaluate.py from dummy033485/multi-agent-rl-navigation: direct link, hf CLI and curl.
- Browser
- Download file 8.44 kB
-
https://huggingface.co/dummy033485/multi-agent-rl-navigation/resolve/main/evaluate.py
- Command line
-
hf download hf://dummy033485/multi-agent-rl-navigation/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/dummy033485/multi-agent-rl-navigation/resolve/main/evaluate.py
8.44 kB
| """Deterministic evaluation: RL (greedy mean action) vs classical. | |
| Layouts are FIXED and identical for every policy/condition: | |
| single-agent: seeds 1000..1019 (10 general + 10 trap, alternating) | |
| 4-agent: seeds 2000..2011 (6 general + 6 trap, alternating) | |
| Conditions (same noise RNG seed per layout/condition for both policies): | |
| clean, obs_noise 0.05, obs_noise 0.10, disturbance 1.0 (accel noise), | |
| obstacle_shift 0.5 (circles +x 0.5, segments +x 0.5, clipped to arena). | |
| Ablation: 4-agent clean, RL with neighbour-observation components | |
| (obs indices 11,12,13) zeroed at evaluation time. | |
| Metrics per policy x condition x agent-count, aggregated over agents: | |
| success_rate, collision_rate, collision_obstacle_rate, | |
| collision_agent_rate, timeout_rate, mean steps-to-goal (successes), | |
| mean path length, mean min clearance (successes), | |
| safety_violation_rate (steps with clearance < 0.15 / active steps). | |
| Trap/general breakdowns are recorded separately in results.json. | |
| Outputs: results.json, eval.csv (one row per agent-episode). | |
| """ | |
| import argparse | |
| import copy | |
| import csv | |
| import json | |
| import numpy as np | |
| import torch | |
| from sim import ARENA, AGENT_R, SAFETY_MARGIN, generate_layout, NavSim | |
| from policies import ActorCritic, classical_action, rl_action | |
| def shifted_layout(layout, shift=0.5): | |
| lay = copy.deepcopy(layout) | |
| lay["circles"][:, 0] = np.clip(lay["circles"][:, 0] + shift, 0.8, 11.2) | |
| seg = lay["segments"].copy() | |
| seg[:, 0] = np.clip(seg[:, 0] + shift, 0.3, 11.7) | |
| seg[:, 2] = np.clip(seg[:, 2] + shift, 0.3, 11.7) | |
| lay["segments"] = seg | |
| return lay | |
| def run_episode(policy, net, layout, n_agents, obs_noise, disturbance, | |
| noise_seed, zero_neighbour=False, shift=0.0): | |
| if shift: | |
| layout = shifted_layout(layout, shift) | |
| sim = NavSim(n_agents, seed=noise_seed) | |
| obs = sim.reset(layout, obs_noise=obs_noise, disturbance=disturbance) | |
| while True: | |
| if policy == "classical": | |
| act = classical_action(obs) | |
| if obs_noise > 0: | |
| # classical uses the same noisy observation as RL | |
| pass | |
| elif policy == "rl": | |
| o = obs.copy() | |
| if zero_neighbour: | |
| o[:, 11:14] = 0.0 | |
| act = rl_action(net, o, deterministic=True) | |
| elif policy == "rl_no_neighbour": | |
| o = obs.copy() | |
| o[:, 11:14] = 0.0 | |
| act = rl_action(net, o, deterministic=True) | |
| else: | |
| raise ValueError(policy) | |
| obs, _, done, _ = sim.step(act) | |
| if done: | |
| break | |
| core = sim.core | |
| rows = [] | |
| for a in range(n_agents): | |
| kind = core.collision_kind[0][a] | |
| rows.append({ | |
| "n_agents": n_agents, | |
| "policy": policy, | |
| "trap": bool(layout["trap"]), | |
| "success": bool(core.success[0, a]), | |
| "collision": bool(core.collision[0, a]), | |
| "collision_obstacle": kind in ("obstacle", "obstacle+agent"), | |
| "collision_agent": kind in ("agent", "obstacle+agent"), | |
| "timeout": bool(not core.success[0, a] and not core.collision[0, a]), | |
| "steps": int(core.steps[0]) if core.success[0, a] else None, | |
| "steps_total": int(core.steps[0]), | |
| "path_length": float(core.path_len[0, a]), | |
| "min_clearance": float(core.min_clearance[0, a]), | |
| "viol_steps": int(core.viol_steps[0, a]), | |
| "active_steps": int(core.active_steps[0, a]), | |
| }) | |
| return rows | |
| def aggregate(rows): | |
| n = len(rows) | |
| succ = [r for r in rows if r["success"]] | |
| return { | |
| "n_agent_episodes": n, | |
| "success_rate": float(np.mean([r["success"] for r in rows])), | |
| "collision_rate": float(np.mean([r["collision"] for r in rows])), | |
| "collision_obstacle_rate": float(np.mean([r["collision_obstacle"] for r in rows])), | |
| "collision_agent_rate": float(np.mean([r["collision_agent"] for r in rows])), | |
| "timeout_rate": float(np.mean([r["timeout"] for r in rows])), | |
| "mean_steps_to_goal_success": float(np.mean([r["steps"] for r in succ])) if succ else None, | |
| "mean_path_length": float(np.mean([r["path_length"] for r in rows])), | |
| "mean_min_clearance_success": float(np.mean([r["min_clearance"] for r in succ])) if succ else None, | |
| "safety_violation_rate": float(sum(r["viol_steps"] for r in rows) / max(sum(r["active_steps"] for r in rows), 1)), | |
| } | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--weights", default="ppo_policy.pt") | |
| ap.add_argument("--out", default="results.json") | |
| ap.add_argument("--csv", default="eval.csv") | |
| args = ap.parse_args() | |
| net = ActorCritic() | |
| net.load_state_dict(torch.load(args.weights, map_location="cpu", weights_only=True)) | |
| net.eval() | |
| conditions = [ | |
| ("clean", dict(obs_noise=0.0, disturbance=0.0, shift=0.0)), | |
| ("obs_noise_0.05", dict(obs_noise=0.05, disturbance=0.0, shift=0.0)), | |
| ("obs_noise_0.10", dict(obs_noise=0.10, disturbance=0.0, shift=0.0)), | |
| ("disturbance_1.0", dict(obs_noise=0.0, disturbance=1.0, shift=0.0)), | |
| ("obstacle_shift_0.5", dict(obs_noise=0.0, disturbance=0.0, shift=0.5)), | |
| ] | |
| all_rows = [] | |
| results = {"protocol": { | |
| "single_agent_layout_seeds": list(range(1000, 1020)), | |
| "four_agent_layout_seeds": list(range(2000, 2012)), | |
| "layout_rule": "alternating general/trap starting with general; same layouts for every policy and condition", | |
| "noise_seed_rule": "90000 + layout_index, same seed per layout/condition for both policies", | |
| "weights": args.weights, | |
| "max_steps": 200, "dt": 0.1, "arena": 12.0, | |
| "safety_margin": SAFETY_MARGIN, | |
| }, "conditions": {}} | |
| for cond_name, cond in conditions: | |
| results["conditions"][cond_name] = {} | |
| for n_agents, seeds in ((1, range(1000, 1020)), (4, range(2000, 2012))): | |
| layouts = [] | |
| for i, s in enumerate(seeds): | |
| rng = np.random.default_rng(s) | |
| layouts.append(generate_layout(rng, n_agents, trap=(i % 2 == 1))) | |
| key = f"{n_agents}_agent" | |
| results["conditions"][cond_name][key] = {} | |
| for policy in ("classical", "rl"): | |
| rows = [] | |
| for li, lay in enumerate(layouts): | |
| rows.extend(run_episode(policy, net, lay, n_agents, | |
| cond["obs_noise"], cond["disturbance"], | |
| noise_seed=90000 + li, shift=cond["shift"])) | |
| for r in rows: | |
| r["condition"] = cond_name | |
| all_rows.extend(rows) | |
| results["conditions"][cond_name][key][policy] = aggregate(rows) | |
| for trap_val, label in ((False, "general"), (True, "trap")): | |
| sub = [r for r in rows if r["trap"] == trap_val] | |
| results["conditions"][cond_name][key][policy][f"breakdown_{label}"] = aggregate(sub) | |
| # ablation: 4-agent clean, RL with neighbour observation zeroed | |
| abl_rows, abl_base = [], [] | |
| for i, s in enumerate(range(2000, 2012)): | |
| rng = np.random.default_rng(s) | |
| lay = generate_layout(rng, 4, trap=(i % 2 == 1)) | |
| abl_rows.extend(run_episode("rl_no_neighbour", net, lay, 4, 0.0, 0.0, 90000 + i)) | |
| abl_base.extend(run_episode("rl", net, lay, 4, 0.0, 0.0, 90000 + i)) | |
| for r in abl_rows: | |
| r["condition"] = "ablation_no_neighbour_4agent_clean" | |
| all_rows.extend(abl_rows) | |
| results["ablation_no_neighbour_4agent_clean"] = { | |
| "rl_with_neighbour": aggregate(abl_base), | |
| "rl_no_neighbour": aggregate(abl_rows), | |
| } | |
| with open(args.out, "w") as f: | |
| json.dump(results, f, indent=2) | |
| with open(args.csv, "w", newline="") as f: | |
| fields = ["condition", "policy", "n_agents", "trap", "success", "collision", | |
| "collision_obstacle", "collision_agent", "timeout", "steps", | |
| "steps_total", "path_length", "min_clearance", "viol_steps", "active_steps"] | |
| w = csv.DictWriter(f, fieldnames=fields) | |
| w.writeheader() | |
| for r in all_rows: | |
| w.writerow({k: r[k] for k in fields}) | |
| print(json.dumps(results["conditions"], indent=2)) | |
| print("ablation:", json.dumps(results["ablation_no_neighbour_4agent_clean"], indent=2)) | |
| if __name__ == "__main__": | |
| main() | |