From 5ffb695aa9bf394eda53afd05231eb9758070512 Mon Sep 17 00:00:00 2001 From: Jon Binney Date: Mon, 7 Jul 2025 12:09:28 -0400 Subject: [PATCH 1/4] Import agents only when needed --- deep_quoridor/src/agents/__init__.py | 38 +-------------- deep_quoridor/src/agents/core/agent.py | 46 ++++++++++++++++--- .../src/agents/nn/pyramid_1024_dropout.py | 5 ++ deep_quoridor/src/agents/sb3_ppo.py | 4 -- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/deep_quoridor/src/agents/__init__.py b/deep_quoridor/src/agents/__init__.py index 2ccccc1e..1c21c615 100644 --- a/deep_quoridor/src/agents/__init__.py +++ b/deep_quoridor/src/agents/__init__.py @@ -3,23 +3,10 @@ "ActionLog", "Agent", "AgentRegistry", - "AlphaZeroOSAgent", - "DExpAgent", - "HumanAgent", - "MCTSAgent", - "NDexpAgent", - "RandomAgent", "ReplayAgent", "ReplayBuffer", - "SimpleAgent", "TrainableAgentParams", ] - - -from agents.adapter_based_agents import Cnn3CAgent, CnnAgent, NDexpAgent -from agents.alphazero import AlphaZeroAgent # noqa: E402, F401 -from agents.alphazero_dexp import DAZAgent -from agents.alphazero_os import AlphaZeroOSAgent # noqa: E402, F401 from agents.core import ( # noqa: E402, F401 # noqa: E402, F401 AbstractTrainableAgent, ActionLog, @@ -28,27 +15,4 @@ ReplayBuffer, TrainableAgentParams, ) -from agents.dexp import DExpAgent # noqa: E402 -from agents.greedy import GreedyAgent # noqa: E402, F401 -from agents.human import HumanAgent # noqa: E402, F401 -from agents.mcts import MCTSAgent # noqa: E402, F401 -from agents.random import RandomAgent # noqa: E402, F401 -from agents.replay import ReplayAgent # noqa: E402, F401 -from agents.sb3_ppo import SB3PPOAgent # noqa: E402, F401 -from agents.simple import SimpleAgent # noqa: E402, F401 - -AgentRegistry.register("alphazero", AlphaZeroAgent) -AgentRegistry.register("alphazero_os", AlphaZeroOSAgent) -AgentRegistry.register("cnn", CnnAgent) -AgentRegistry.register("cnn3c", Cnn3CAgent) -AgentRegistry.register("daz", DAZAgent) -AgentRegistry.register("daz_mimic", DAZAgent.create_from_trained_instance) -AgentRegistry.register("dexp", DExpAgent) -AgentRegistry.register("dexp_mimic", DExpAgent.create_from_trained_instance) -AgentRegistry.register("greedy", GreedyAgent) -AgentRegistry.register("human", HumanAgent) -AgentRegistry.register("mcts", MCTSAgent) -AgentRegistry.register("ndexp", NDexpAgent) -AgentRegistry.register("random", RandomAgent) -AgentRegistry.register("simple", SimpleAgent) -AgentRegistry.register("sb3ppo", SB3PPOAgent) +from agents.replay import ReplayAgent diff --git a/deep_quoridor/src/agents/core/agent.py b/deep_quoridor/src/agents/core/agent.py index dab095c2..f9200a53 100644 --- a/deep_quoridor/src/agents/core/agent.py +++ b/deep_quoridor/src/agents/core/agent.py @@ -1,5 +1,7 @@ +import importlib from abc import ABC, abstractmethod from dataclasses import dataclass +from typing import Type from quoridor import Action from utils import parse_subargs @@ -113,8 +115,41 @@ def get_action(self, observation) -> int: pass +@dataclass +class AgentRegistryEntry: + class_name: str + module_name: str + agent_class: Type[Agent] = None + + class AgentRegistry: - agents = {} + agents = { + "alphazero": AgentRegistryEntry("AlphaZeroAgent", "agents.alphazero"), + "alphazero_os": AgentRegistryEntry("AlphaZeroOSAgent", "agents.alphazero_os"), + "cnn": AgentRegistryEntry("CnnAgent", "agents.adapter_based_agents"), + "cnn3c": AgentRegistryEntry("Cnn3CAgent", "agents.adapter_based_agents"), + "daz": AgentRegistryEntry("DAZAgent", "agents.alphazero_dexp"), + "daz_mimic": AgentRegistryEntry("DAZAgent.create_from_trained_instance", ""), + "dexp": AgentRegistryEntry("DExpAgent", "agents.dexp"), + "dexp_mimic": AgentRegistryEntry("DExpAgent.create_from_trained_instance", ""), + "greedy": AgentRegistryEntry("GreedyAgent", "agents.greedy"), + "human": AgentRegistryEntry("HumanAgent", "agents.human"), + "mcts": AgentRegistryEntry("MCTSAgent", "agents.mcts"), + "ndexp": AgentRegistryEntry("NDexpAgent", "agents.adapter_based_agents"), + "random": AgentRegistryEntry("RandomAgent", "agents.random"), + "simple": AgentRegistryEntry("SimpleAgent", "agents.simple"), + "sb3ppo": AgentRegistryEntry("SB3PPOAgent", "agents.sb3_ppo"), + } + + @staticmethod + def get_agent_class(agent_type: str) -> Type[Agent]: + registry_entry = AgentRegistry.agents[agent_type] + + if registry_entry.agent_class is None: + agent_module = importlib.import_module(registry_entry.module_name) + registry_entry.agent_class = getattr(agent_module, registry_entry.class_name) + + return registry_entry.agent_class @staticmethod def create(friendly_name: str, **kwargs) -> Agent: @@ -126,8 +161,9 @@ def create_from_encoded_name( ) -> Agent: parts = encoded_name.split(":") agent_type = parts[0] + agent_class = AgentRegistry.get_agent_class(agent_type) if len(parts) == 2: - subargs_class = AgentRegistry.agents[agent_type].params_class() + subargs_class = agent_class.params_class() if subargs_class is None: raise ValueError(f"The agent {agent_type} doesn't support subarguments, but '{parts[1]}' was passed") @@ -138,7 +174,7 @@ def create_from_encoded_name( subargs = parse_subargs(parts[1], subargs_class) kwargs["params"] = subargs - return AgentRegistry.agents[agent_type]( + return agent_class( board_size=env.board_size, max_walls=env.max_walls, observation_space=env.observation_space(None), @@ -155,7 +191,3 @@ def is_valid_encoded_name(encoded_name: str): @staticmethod def names(): return list(AgentRegistry.agents.keys()) - - @staticmethod - def register(name: str, agent_class): - AgentRegistry.agents[name] = agent_class diff --git a/deep_quoridor/src/agents/nn/pyramid_1024_dropout.py b/deep_quoridor/src/agents/nn/pyramid_1024_dropout.py index c76439ee..f957d7f8 100644 --- a/deep_quoridor/src/agents/nn/pyramid_1024_dropout.py +++ b/deep_quoridor/src/agents/nn/pyramid_1024_dropout.py @@ -62,6 +62,11 @@ def observation_to_tensor(self, observation): for key, value in observation.items(): if isinstance(value, np.ndarray): flat_obs.extend(value.flatten()) + elif key == "player_turn": + if value == "player_0": + value = 0 + else: + value = 1 else: flat_obs.append(value) return torch.FloatTensor(flat_obs).to(my_device()) diff --git a/deep_quoridor/src/agents/sb3_ppo.py b/deep_quoridor/src/agents/sb3_ppo.py index 3f01feb7..9e726f70 100644 --- a/deep_quoridor/src/agents/sb3_ppo.py +++ b/deep_quoridor/src/agents/sb3_ppo.py @@ -292,7 +292,3 @@ def forward(self, obs: dict) -> torch.Tensor: for v in obs.values(): thobs = torch.cat((thobs, torch.tensor(v).flatten(start_dim=1)), dim=1) return thobs - - -# Register the agent with the registry -AgentRegistry.register(Agent._friendly_name(SB3PPOAgent.__name__), SB3PPOAgent) From 1a01334c278513fa601ea2a8e99c1fa9af099078 Mon Sep 17 00:00:00 2001 From: Jon Binney Date: Mon, 7 Jul 2025 13:10:20 -0400 Subject: [PATCH 2/4] Register agents in agents init file --- deep_quoridor/src/agents/__init__.py | 16 ++++++++++++++++ deep_quoridor/src/agents/core/agent.py | 22 +++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/deep_quoridor/src/agents/__init__.py b/deep_quoridor/src/agents/__init__.py index 1c21c615..9c51bd15 100644 --- a/deep_quoridor/src/agents/__init__.py +++ b/deep_quoridor/src/agents/__init__.py @@ -16,3 +16,19 @@ TrainableAgentParams, ) from agents.replay import ReplayAgent + +AgentRegistry.register("alphazero", "AlphaZeroAgent", "agents.alphazero") +AgentRegistry.register("alphazero_os", "AlphaZeroOSAgent", "agents.alphazero_os") +AgentRegistry.register("cnn", "CnnAgent", "agents.adapter_based_agents") +AgentRegistry.register("cnn3c", "Cnn3CAgent", "agents.adapter_based_agents") +AgentRegistry.register("daz", "DAZAgent", "agents.alphazero_dexp") +AgentRegistry.register("daz_mimic", "DAZAgent.create_from_trained_instance", "") +AgentRegistry.register("dexp", "DExpAgent", "agents.dexp") +AgentRegistry.register("dexp_mimic", "DExpAgent.create_from_trained_instance", "") +AgentRegistry.register("greedy", "GreedyAgent", "agents.greedy") +AgentRegistry.register("human", "HumanAgent", "agents.human") +AgentRegistry.register("mcts", "MCTSAgent", "agents.mcts") +AgentRegistry.register("ndexp", "NDexpAgent", "agents.adapter_based_agents") +AgentRegistry.register("random", "RandomAgent", "agents.random") +AgentRegistry.register("simple", "SimpleAgent", "agents.simple") +AgentRegistry.register("sb3ppo", "SB3PPOAgent", "agents.sb3_ppo") diff --git a/deep_quoridor/src/agents/core/agent.py b/deep_quoridor/src/agents/core/agent.py index f9200a53..97ab28dd 100644 --- a/deep_quoridor/src/agents/core/agent.py +++ b/deep_quoridor/src/agents/core/agent.py @@ -123,23 +123,7 @@ class AgentRegistryEntry: class AgentRegistry: - agents = { - "alphazero": AgentRegistryEntry("AlphaZeroAgent", "agents.alphazero"), - "alphazero_os": AgentRegistryEntry("AlphaZeroOSAgent", "agents.alphazero_os"), - "cnn": AgentRegistryEntry("CnnAgent", "agents.adapter_based_agents"), - "cnn3c": AgentRegistryEntry("Cnn3CAgent", "agents.adapter_based_agents"), - "daz": AgentRegistryEntry("DAZAgent", "agents.alphazero_dexp"), - "daz_mimic": AgentRegistryEntry("DAZAgent.create_from_trained_instance", ""), - "dexp": AgentRegistryEntry("DExpAgent", "agents.dexp"), - "dexp_mimic": AgentRegistryEntry("DExpAgent.create_from_trained_instance", ""), - "greedy": AgentRegistryEntry("GreedyAgent", "agents.greedy"), - "human": AgentRegistryEntry("HumanAgent", "agents.human"), - "mcts": AgentRegistryEntry("MCTSAgent", "agents.mcts"), - "ndexp": AgentRegistryEntry("NDexpAgent", "agents.adapter_based_agents"), - "random": AgentRegistryEntry("RandomAgent", "agents.random"), - "simple": AgentRegistryEntry("SimpleAgent", "agents.simple"), - "sb3ppo": AgentRegistryEntry("SB3PPOAgent", "agents.sb3_ppo"), - } + agents = {} @staticmethod def get_agent_class(agent_type: str) -> Type[Agent]: @@ -191,3 +175,7 @@ def is_valid_encoded_name(encoded_name: str): @staticmethod def names(): return list(AgentRegistry.agents.keys()) + + @staticmethod + def register(name: str, class_name: str, module_name: str): + AgentRegistry.agents[name] = AgentRegistryEntry(class_name, module_name) From eddf1d751632a843c35c953a57beb8b06e37d345 Mon Sep 17 00:00:00 2001 From: Jon Binney Date: Tue, 8 Jul 2025 09:26:46 -0400 Subject: [PATCH 3/4] First pass at fixing mimic imports --- deep_quoridor/src/agents/__init__.py | 4 +-- deep_quoridor/src/agents/core/agent.py | 46 ++++++++++++++++++-------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/deep_quoridor/src/agents/__init__.py b/deep_quoridor/src/agents/__init__.py index 9c51bd15..121cb4d3 100644 --- a/deep_quoridor/src/agents/__init__.py +++ b/deep_quoridor/src/agents/__init__.py @@ -22,9 +22,9 @@ AgentRegistry.register("cnn", "CnnAgent", "agents.adapter_based_agents") AgentRegistry.register("cnn3c", "Cnn3CAgent", "agents.adapter_based_agents") AgentRegistry.register("daz", "DAZAgent", "agents.alphazero_dexp") -AgentRegistry.register("daz_mimic", "DAZAgent.create_from_trained_instance", "") +AgentRegistry.register("daz_mimic", "DAZAgent.create_from_trained_instance", "agents.alphazero_dexp") AgentRegistry.register("dexp", "DExpAgent", "agents.dexp") -AgentRegistry.register("dexp_mimic", "DExpAgent.create_from_trained_instance", "") +AgentRegistry.register("dexp_mimic", "DExpAgent.create_from_trained_instance", "agents.dexp") AgentRegistry.register("greedy", "GreedyAgent", "agents.greedy") AgentRegistry.register("human", "HumanAgent", "agents.human") AgentRegistry.register("mcts", "MCTSAgent", "agents.mcts") diff --git a/deep_quoridor/src/agents/core/agent.py b/deep_quoridor/src/agents/core/agent.py index 97ab28dd..756195ff 100644 --- a/deep_quoridor/src/agents/core/agent.py +++ b/deep_quoridor/src/agents/core/agent.py @@ -1,10 +1,10 @@ import importlib from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Type +from typing import Optional, Type from quoridor import Action -from utils import parse_subargs +from utils import SubargsBase, parse_subargs class ActionLog: @@ -119,21 +119,42 @@ def get_action(self, observation) -> int: class AgentRegistryEntry: class_name: str module_name: str - agent_class: Type[Agent] = None + agent_class: Optional[Type[Agent]] = None + params_class: Optional[Type[SubargsBase]] = None class AgentRegistry: agents = {} @staticmethod - def get_agent_class(agent_type: str) -> Type[Agent]: + def get_registry_entry(agent_type: str) -> AgentRegistryEntry: registry_entry = AgentRegistry.agents[agent_type] if registry_entry.agent_class is None: agent_module = importlib.import_module(registry_entry.module_name) - registry_entry.agent_class = getattr(agent_module, registry_entry.class_name) + fields = registry_entry.class_name.split(".") - return registry_entry.agent_class + if len(fields) == 1: + registry_entry.agent_class = getattr(agent_module, registry_entry.class_name) + + if hasattr(registry_entry.agent_class, "params_class"): + registry_entry.params_class = registry_entry.agent_class.params_class() + + elif len(fields) == 2: + # Some agents register a static creation member function instead of the class itself. + # Something like FooAgent.create_a_foo which returns a FooAgent when called. We pretend this + # creation function is the class, and use it as RegistryEntry.agent_class. + class_name = fields[0] + creation_function = fields[1] + actual_agent_class = getattr(agent_module, class_name) + registry_entry.agent_class = registry_entry.agent_class = getattr(actual_agent_class, creation_function) + + if hasattr(actual_agent_class, "params_class"): + registry_entry.params_class = actual_agent_class.params_class() + else: + raise ValueError(f"Invalid class name for agent: {registry_entry.class_name}") + + return registry_entry @staticmethod def create(friendly_name: str, **kwargs) -> Agent: @@ -145,20 +166,19 @@ def create_from_encoded_name( ) -> Agent: parts = encoded_name.split(":") agent_type = parts[0] - agent_class = AgentRegistry.get_agent_class(agent_type) + registry_entry = AgentRegistry.get_registry_entry(agent_type) if len(parts) == 2: - subargs_class = agent_class.params_class() - if subargs_class is None: + if registry_entry.params_class is None: raise ValueError(f"The agent {agent_type} doesn't support subarguments, but '{parts[1]}' was passed") if remove_training_args: - args_to_remove = subargs_class.training_only_params().difference(keep_args) - subargs = parse_subargs(parts[1], subargs_class, ignore_fields=args_to_remove) + args_to_remove = registry_entry.agent_class.training_only_params().difference(keep_args) + subargs = parse_subargs(parts[1], registry_entry.params_class, ignore_fields=args_to_remove) else: - subargs = parse_subargs(parts[1], subargs_class) + subargs = parse_subargs(parts[1], registry_entry.params_class) kwargs["params"] = subargs - return agent_class( + return registry_entry.agent_class( board_size=env.board_size, max_walls=env.max_walls, observation_space=env.observation_space(None), From fc3ede4fe8afb88566959a06e8fe6ba47fcb203e Mon Sep 17 00:00:00 2001 From: Jon Binney Date: Tue, 8 Jul 2025 10:07:24 -0400 Subject: [PATCH 4/4] Cleanup fix for mimic agents --- deep_quoridor/src/agents/core/agent.py | 28 +++++++++----------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/deep_quoridor/src/agents/core/agent.py b/deep_quoridor/src/agents/core/agent.py index 756195ff..7daaa6b4 100644 --- a/deep_quoridor/src/agents/core/agent.py +++ b/deep_quoridor/src/agents/core/agent.py @@ -132,27 +132,16 @@ def get_registry_entry(agent_type: str) -> AgentRegistryEntry: if registry_entry.agent_class is None: agent_module = importlib.import_module(registry_entry.module_name) - fields = registry_entry.class_name.split(".") + element_names = registry_entry.class_name.split(".") - if len(fields) == 1: - registry_entry.agent_class = getattr(agent_module, registry_entry.class_name) + # If the class_name is heirarchical, e.g. "foo.bar.Baz", then we need to gettatr + # the first element, then the second, etc. until we get to Baz. + registry_entry.agent_class = getattr(agent_module, element_names[0]) + for element_name in element_names[1:]: + registry_entry.agent_class = getattr(registry_entry.agent_class, element_name) - if hasattr(registry_entry.agent_class, "params_class"): - registry_entry.params_class = registry_entry.agent_class.params_class() - - elif len(fields) == 2: - # Some agents register a static creation member function instead of the class itself. - # Something like FooAgent.create_a_foo which returns a FooAgent when called. We pretend this - # creation function is the class, and use it as RegistryEntry.agent_class. - class_name = fields[0] - creation_function = fields[1] - actual_agent_class = getattr(agent_module, class_name) - registry_entry.agent_class = registry_entry.agent_class = getattr(actual_agent_class, creation_function) - - if hasattr(actual_agent_class, "params_class"): - registry_entry.params_class = actual_agent_class.params_class() - else: - raise ValueError(f"Invalid class name for agent: {registry_entry.class_name}") + if hasattr(registry_entry.agent_class, "params_class"): + registry_entry.params_class = registry_entry.agent_class.params_class() return registry_entry @@ -176,6 +165,7 @@ def create_from_encoded_name( subargs = parse_subargs(parts[1], registry_entry.params_class, ignore_fields=args_to_remove) else: subargs = parse_subargs(parts[1], registry_entry.params_class) + kwargs["params"] = subargs return registry_entry.agent_class(