| from collections import defaultdict, namedtuple |
| import math |
| from pathlib import Path |
| from typing import Any, Dict, List, Tuple |
|
|
| import pygame |
| import torch |
| from torch import Tensor |
|
|
| from agent import Agent |
| from data import Dataset, Episode |
| from game.keymap import ActionNames, Keymap |
| from envs import WorldModelEnv |
|
|
|
|
| NamedEnv = namedtuple("NamedEnv", "name env") |
| OneStepData = namedtuple("OneStepData", "obs act rew end trunc") |
|
|
|
|
| class PlayEnv: |
| def __init__( |
| self, |
| agent: Agent, |
| envs: List[NamedEnv], |
| action_names: ActionNames, |
| keymap: Keymap, |
| recording_mode: bool, |
| store_denoising_trajectory: bool, |
| store_original_obs: bool, |
| ) -> None: |
| self.agent = agent |
| self.envs = envs |
| self.action_names = action_names |
| self.keymap = keymap |
| self.recording_mode = recording_mode |
| self.store_denoising_trajectory = store_denoising_trajectory |
| self.store_original_obs = store_original_obs |
| self.is_human_player = False |
| self.env_id = 0 |
| self.env_name, self.env = self.envs[0] |
| self.obs, self.t, self.return_, self.hx_cx, self.ckpt_id, self.buffer, self.rec_dataset = (None,) * 7 |
|
|
| def print_controls(self) -> None: |
| print("\nControls (play mode):\n") |
| print("m : controller (policy/human)") |
| print("↑ : imagination horizon (+1)") |
| print("↓ : imagination horizon (-1)") |
| print(f"→ : next environment ({' → '.join([env_name for (env_name, _) in self.envs])})") |
| print(f"← : prev environment ({' ← '.join([env_name for (env_name, _) in self.envs])})") |
| print("\nEnvironment actions:\n") |
| for keys, idx in self.keymap.items(): |
| key_names = [pygame.key.name(key) for key in keys] |
| key_names = ["⎵" if key_name == "space" else key_name for key_name in key_names] |
| print(f"{' + '.join(key_names)} : {self.action_names[idx]}") |
|
|
| def next_mode(self) -> bool: |
| self.switch_controller() |
| return True |
|
|
| def next_axis_1(self) -> bool: |
| self.update_wm_horizon(+1) |
| return True |
|
|
| def prev_axis_1(self) -> bool: |
| self.update_wm_horizon(-1) |
| return True |
|
|
| def next_axis_2(self) -> bool: |
| self.switch_env(self.env_id + 1) |
| return True |
|
|
| def prev_axis_2(self) -> bool: |
| self.switch_env(self.env_id - 1) |
| return True |
|
|
| def is_wm_env(self) -> bool: |
| return isinstance(self.env, WorldModelEnv) |
|
|
| def print_env(self) -> None: |
| print(f"> Environment: {self.env_name}") |
|
|
| def print_control(self) -> None: |
| print(f"> Control: {'human' if self.is_human_player else 'policy'}") |
|
|
| def switch_env(self, env_id: int) -> None: |
| self.env_id = env_id % len(self.envs) |
| self.env_name, self.env = self.envs[self.env_id] |
| self.print_env() |
|
|
| def switch_controller(self) -> None: |
| self.is_human_player = not self.is_human_player |
| self.print_control() |
|
|
| def update_wm_horizon(self, incr: int) -> None: |
| if self.is_wm_env(): |
| self.env.horizon = max(1, self.env.horizon + incr) |
|
|
| def reset_recording(self) -> None: |
| self.buffer = defaultdict(list) |
| self.buffer["info"] = defaultdict(list) |
| dir = Path("dataset") / f"rec_{self.env_name}_{'H' if self.is_human_player else 'π'}" |
| self.rec_dataset = Dataset(dir) |
| self.rec_dataset.load_from_default_path() |
|
|
| def reset(self) -> Tuple[Tensor, None]: |
| self.obs, _ = self.env.reset() |
| self.t, self.return_, self.hx_cx = 0, 0, None |
| if self.recording_mode: |
| self.reset_recording() |
| return self.obs, None |
|
|
| @torch.no_grad() |
| def step(self, act: int) -> Tuple[Tensor, Tensor, Tensor, Tensor, Dict[str, Any]]: |
| if self.is_human_player: |
| act = torch.tensor([act], device=self.agent.device) |
| else: |
| logits_act, value, self.hx_cx = self.agent.actor_critic.predict_act_value(self.obs, self.hx_cx) |
| dst = torch.distributions.categorical.Categorical(logits=logits_act) |
| act = dst.sample() |
| entropy = dst.entropy() / math.log(2) |
| entropy = None if self.is_human_player else f"{entropy.item():.2f}" |
| value = None if self.is_human_player else f"{value.item():.2f}" |
| next_obs, rew, end, trunc, env_info = self.env.step(act) |
| data = OneStepData(self.obs, act, rew, end, trunc) |
| self.return_ += rew.item() |
| control = "human" if self.is_human_player else "policy" |
| header = [ |
| [ |
| f"Env : {self.env_name}", |
| f"Control : {control}", |
| f"Timestep: {self.t + 1}", |
| f"Horizon : {self.env.horizon}" if self.is_wm_env() else "", |
| ], |
| [ |
| f"Trunc : {bool(trunc)}", |
| f"Done : {bool(end)}", |
| f"Reward: {rew.item():.2f}", |
| f"Return: {self.return_:.2f}", |
| ], |
| [ |
| f"Action : {self.action_names[act[0]]}", |
| f"Entropy: {entropy}", |
| f"Value : {value}", |
| ], |
| ] |
| info = {"header": header} |
|
|
| if end or trunc: |
| d = "Dead" if end else ("Horizon" if self.is_wm_env() else "Timed out") |
| print(f"[{d}] return = {self.return_} - length = {self.t + 1}.") |
|
|
| if self.recording_mode: |
| for k, v in data._asdict().items(): |
| self.buffer[k].append(v) |
| if self.store_denoising_trajectory and "denoising_trajectory" in env_info: |
| self.buffer["info"]["denoising_trajectory"].append(env_info["denoising_trajectory"]) |
| if self.store_original_obs and "original_obs" in env_info: |
| original_obs = (torch.tensor(env_info["original_obs"][0]).permute(2, 0, 1).unsqueeze(0).contiguous()) |
| self.buffer["info"]["original_obs"].append(original_obs) |
| if end or trunc: |
| ep_dict = {k: torch.cat(v, dim=0) for k, v in self.buffer.items() if k != "info"} |
| ep_info = {k: torch.cat(v, dim=0) for k, v in self.buffer["info"].items()} |
| ep = Episode(**ep_dict, info=ep_info).to("cpu") |
| self.rec_dataset.add_episode(ep) |
| self.rec_dataset.save_to_default_path() |
|
|
| self.obs = next_obs |
| self.t += 1 |
|
|
| return next_obs, rew, end, trunc, info |
|
|