From f8336d07bdfcc276d165541716d258bdf0670e21 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 31 May 2024 13:28:56 +0100 Subject: [PATCH 1/6] #2626 fix too many open files bug --- src/primaite/session/environment.py | 2 ++ src/primaite/session/ray_envs.py | 2 ++ .../simulator/system/core/packet_capture.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py index edb8a476..6bcadab6 100644 --- a/src/primaite/session/environment.py +++ b/src/primaite/session/environment.py @@ -11,6 +11,7 @@ from primaite.game.game import PrimaiteGame from primaite.session.episode_schedule import build_scheduler, EpisodeScheduler from primaite.session.io import PrimaiteIO from primaite.simulator import SIM_OUTPUT +from primaite.simulator.system.core.packet_capture import PacketCapture _LOGGER = getLogger(__name__) @@ -92,6 +93,7 @@ class PrimaiteGymEnv(gymnasium.Env): all_agent_actions = {name: agent.action_history for name, agent in self.game.agents.items()} self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) self.episode_counter += 1 + PacketCapture.clear() self.game: PrimaiteGame = PrimaiteGame.from_config(cfg=self.episode_scheduler(self.episode_counter)) self.game.setup_for_episode(episode=self.episode_counter) state = self.game.get_sim_state() diff --git a/src/primaite/session/ray_envs.py b/src/primaite/session/ray_envs.py index 5149a225..f4691155 100644 --- a/src/primaite/session/ray_envs.py +++ b/src/primaite/session/ray_envs.py @@ -11,6 +11,7 @@ from primaite.session.environment import _LOGGER, PrimaiteGymEnv from primaite.session.episode_schedule import build_scheduler, EpisodeScheduler from primaite.session.io import PrimaiteIO from primaite.simulator import SIM_OUTPUT +from primaite.simulator.system.core.packet_capture import PacketCapture class PrimaiteRayMARLEnv(MultiAgentEnv): @@ -63,6 +64,7 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) self.episode_counter += 1 + PacketCapture.clear() self.game: PrimaiteGame = PrimaiteGame.from_config(self.episode_scheduler(self.episode_counter)) self.game.setup_for_episode(episode=self.episode_counter) state = self.game.get_sim_state() diff --git a/src/primaite/simulator/system/core/packet_capture.py b/src/primaite/simulator/system/core/packet_capture.py index cf38e94b..bc8a0584 100644 --- a/src/primaite/simulator/system/core/packet_capture.py +++ b/src/primaite/simulator/system/core/packet_capture.py @@ -21,6 +21,8 @@ class PacketCapture: The PCAPs are logged to: //__pcap.log """ + _logger_instances: List[logging.Logger] = [] + def __init__( self, hostname: str, @@ -65,10 +67,12 @@ class PacketCapture: if outbound: self.outbound_logger = logging.getLogger(self._get_logger_name(outbound)) + PacketCapture._logger_instances.append(self.outbound_logger) logger = self.outbound_logger else: self.inbound_logger = logging.getLogger(self._get_logger_name(outbound)) logger = self.inbound_logger + PacketCapture._logger_instances.append(self.inbound_logger) logger.setLevel(60) # Custom log level > CRITICAL to prevent any unwanted standard DEBUG-CRITICAL logs logger.addHandler(file_handler) @@ -122,3 +126,13 @@ class PacketCapture: if SIM_OUTPUT.save_pcap_logs: msg = frame.model_dump_json() self.outbound_logger.log(level=60, msg=msg) # Log at custom log level > CRITICAL + + @staticmethod + def clear(): + """Close all open PCAP file handlers.""" + for logger in PacketCapture._logger_instances: + handlers = logger.handlers[:] + for handler in handlers: + logger.removeHandler(handler) + handler.close() + PacketCapture._logger_instances = [] From c5f131ece59eef137efaee89a141584aca4ae78a Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 31 May 2024 15:00:18 +0100 Subject: [PATCH 2/6] fix reward logging --- src/primaite/game/agent/interface.py | 16 +++++++++++----- src/primaite/game/agent/rewards.py | 18 +++++++++--------- src/primaite/game/game.py | 1 + ...ta-Manipulation-Customising-Red-Agent.ipynb | 4 ++-- .../Data-Manipulation-E2E-Demonstration.ipynb | 6 +++--- .../Training-an-RLLIB-MARL-System.ipynb | 2 +- .../notebooks/Using-Episode-Schedules.ipynb | 8 ++++---- src/primaite/session/environment.py | 10 +++++----- src/primaite/session/io.py | 2 +- src/primaite/session/ray_envs.py | 8 ++++---- .../game_layer/test_rewards.py | 6 +++--- 11 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index cd4a1c29..444aa4f7 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: pass -class AgentActionHistoryItem(BaseModel): +class AgentHistoryItem(BaseModel): """One entry of an agent's action log - what the agent did and how the simulator responded in 1 step.""" timestep: int @@ -32,6 +32,8 @@ class AgentActionHistoryItem(BaseModel): response: RequestResponse """The response sent back by the simulator for this action.""" + reward: Optional[float] = None + class AgentStartSettings(BaseModel): """Configuration values for when an agent starts performing actions.""" @@ -110,7 +112,7 @@ class AbstractAgent(ABC): self.observation_manager: Optional[ObservationManager] = observation_space self.reward_function: Optional[RewardFunction] = reward_function self.agent_settings = agent_settings or AgentSettings() - self.action_history: List[AgentActionHistoryItem] = [] + self.history: List[AgentHistoryItem] = [] def update_observation(self, state: Dict) -> ObsType: """ @@ -130,7 +132,7 @@ class AbstractAgent(ABC): :return: Reward from the state. :rtype: float """ - return self.reward_function.update(state=state, last_action_response=self.action_history[-1]) + return self.reward_function.update(state=state, last_action_response=self.history[-1]) @abstractmethod def get_action(self, obs: ObsType, timestep: int = 0) -> Tuple[str, Dict]: @@ -161,12 +163,16 @@ class AbstractAgent(ABC): self, timestep: int, action: str, parameters: Dict[str, Any], request: RequestFormat, response: RequestResponse ) -> None: """Process the response from the most recent action.""" - self.action_history.append( - AgentActionHistoryItem( + self.history.append( + AgentHistoryItem( timestep=timestep, action=action, parameters=parameters, request=request, response=response ) ) + def save_reward_to_history(self) -> None: + """Update the most recent history item with the reward value.""" + self.history[-1].reward = self.reward_function.current_reward + class AbstractScriptedAgent(AbstractAgent): """Base class for actors which generate their own behaviour.""" diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 0222bfcc..d77640d1 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -34,7 +34,7 @@ from primaite import getLogger from primaite.game.agent.utils import access_from_nested_dict, NOT_PRESENT_IN_STATE if TYPE_CHECKING: - from primaite.game.agent.interface import AgentActionHistoryItem + from primaite.game.agent.interface import AgentHistoryItem _LOGGER = getLogger(__name__) WhereType = Optional[Iterable[Union[str, int]]] @@ -44,7 +44,7 @@ class AbstractReward: """Base class for reward function components.""" @abstractmethod - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state.""" return 0.0 @@ -64,7 +64,7 @@ class AbstractReward: class DummyReward(AbstractReward): """Dummy reward function component which always returns 0.""" - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state.""" return 0.0 @@ -104,7 +104,7 @@ class DatabaseFileIntegrity(AbstractReward): file_name, ] - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. :param state: The current state of the simulation. @@ -159,7 +159,7 @@ class WebServer404Penalty(AbstractReward): """ self.location_in_state = ["network", "nodes", node_hostname, "services", service_name] - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. :param state: The current state of the simulation. @@ -213,7 +213,7 @@ class WebpageUnavailablePenalty(AbstractReward): self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "WebBrowser"] self._last_request_failed: bool = False - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ Calculate the reward based on current simulation state, and the recent agent action. @@ -273,7 +273,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward): self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "DatabaseClient"] self._last_request_failed: bool = False - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ Calculate the reward based on current simulation state, and the recent agent action. @@ -343,7 +343,7 @@ class SharedReward(AbstractReward): self.callback: Callable[[str], float] = default_callback """Method that retrieves an agent's current reward given the agent's name.""" - def calculate(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Simply access the other agent's reward and return it.""" return self.callback(self.agent_name) @@ -389,7 +389,7 @@ class RewardFunction: """ self.reward_components.append((component, weight)) - def update(self, state: Dict, last_action_response: "AgentActionHistoryItem") -> float: + def update(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the overall reward for the current state. :param state: The current state of the simulation. diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index ea5b3831..772ab5aa 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -160,6 +160,7 @@ class PrimaiteGame: agent = self.agents[agent_name] if self.step_counter > 0: # can't get reward before first action agent.update_reward(state=state) + agent.save_reward_to_history() agent.update_observation(state=state) # order of this doesn't matter so just use reward order agent.reward_function.total_reward += agent.reward_function.current_reward diff --git a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb index 1b016bb8..21d67bab 100644 --- a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb @@ -22,7 +22,7 @@ "# Imports\n", "\n", "from primaite.config.load import data_manipulation_config_path\n", - "from primaite.game.agent.interface import AgentActionHistoryItem\n", + "from primaite.game.agent.interface import AgentHistoryItem\n", "from primaite.session.environment import PrimaiteGymEnv\n", "import yaml\n", "from pprint import pprint" @@ -63,7 +63,7 @@ "source": [ "def friendly_output_red_action(info):\n", " # parse the info dict form step output and write out what the red agent is doing\n", - " red_info : AgentActionHistoryItem = info['agent_actions']['data_manipulation_attacker']\n", + " red_info : AgentHistoryItem = info['agent_actions']['data_manipulation_attacker']\n", " red_action = red_info.action\n", " if red_action == 'DONOTHING':\n", " red_str = 'DO NOTHING'\n", diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb index 8104149e..376b7f28 100644 --- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb @@ -392,7 +392,7 @@ "# Imports\n", "from primaite.config.load import data_manipulation_config_path\n", "from primaite.session.environment import PrimaiteGymEnv\n", - "from primaite.game.agent.interface import AgentActionHistoryItem\n", + "from primaite.game.agent.interface import AgentHistoryItem\n", "import yaml\n", "from pprint import pprint\n" ] @@ -444,7 +444,7 @@ "source": [ "def friendly_output_red_action(info):\n", " # parse the info dict form step output and write out what the red agent is doing\n", - " red_info : AgentActionHistoryItem = info['agent_actions']['data_manipulation_attacker']\n", + " red_info : AgentHistoryItem = info['agent_actions']['data_manipulation_attacker']\n", " red_action = red_info.action\n", " if red_action == 'DONOTHING':\n", " red_str = 'DO NOTHING'\n", @@ -705,7 +705,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb index 65b1595f..61b988c6 100644 --- a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb +++ b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb @@ -25,7 +25,7 @@ "from primaite.game.game import PrimaiteGame\n", "import yaml\n", "\n", - "from primaite.session.environment import PrimaiteRayEnv\n", + "from primaite.session.ray_envs import PrimaiteRayEnv\n", "from primaite import PRIMAITE_PATHS\n", "\n", "import ray\n", diff --git a/src/primaite/notebooks/Using-Episode-Schedules.ipynb b/src/primaite/notebooks/Using-Episode-Schedules.ipynb index b0669472..062c7135 100644 --- a/src/primaite/notebooks/Using-Episode-Schedules.ipynb +++ b/src/primaite/notebooks/Using-Episode-Schedules.ipynb @@ -298,8 +298,8 @@ "table = PrettyTable()\n", "table.field_names = [\"step\", \"Green Action\", \"Red Action\"]\n", "for i in range(21):\n", - " green_action = env.game.agents['green_A'].action_history[i].action\n", - " red_action = env.game.agents['red_A'].action_history[i].action\n", + " green_action = env.game.agents['green_A'].history[i].action\n", + " red_action = env.game.agents['red_A'].history[i].action\n", " table.add_row([i, green_action, red_action])\n", "print(table)" ] @@ -329,8 +329,8 @@ "table = PrettyTable()\n", "table.field_names = [\"step\", \"Green Action\", \"Red Action\"]\n", "for i in range(21):\n", - " green_action = env.game.agents['green_B'].action_history[i].action\n", - " red_action = env.game.agents['red_B'].action_history[i].action\n", + " green_action = env.game.agents['green_B'].history[i].action\n", + " red_action = env.game.agents['red_B'].history[i].action\n", " table.add_row([i, green_action, red_action])\n", "print(table)" ] diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py index edb8a476..52edbbb8 100644 --- a/src/primaite/session/environment.py +++ b/src/primaite/session/environment.py @@ -60,7 +60,7 @@ class PrimaiteGymEnv(gymnasium.Env): terminated = False truncated = self.game.calculate_truncated() info = { - "agent_actions": {name: agent.action_history[-1] for name, agent in self.game.agents.items()} + "agent_actions": {name: agent.history[-1] for name, agent in self.game.agents.items()} } # tell us what all the agents did for convenience. if self.game.save_step_metadata: self._write_step_metadata_json(step, action, state, reward) @@ -89,8 +89,8 @@ class PrimaiteGymEnv(gymnasium.Env): f"avg. reward: {self.agent.reward_function.total_reward}" ) if self.io.settings.save_agent_actions: - all_agent_actions = {name: agent.action_history for name, agent in self.game.agents.items()} - self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) + all_agent_actions = {name: agent.history for name, agent in self.game.agents.items()} + self.io.write_agent_log(agent_actions=all_agent_actions, episode=self.episode_counter) self.episode_counter += 1 self.game: PrimaiteGame = PrimaiteGame.from_config(cfg=self.episode_scheduler(self.episode_counter)) self.game.setup_for_episode(episode=self.episode_counter) @@ -125,5 +125,5 @@ class PrimaiteGymEnv(gymnasium.Env): def close(self): """Close the simulation.""" if self.io.settings.save_agent_actions: - all_agent_actions = {name: agent.action_history for name, agent in self.game.agents.items()} - self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) + all_agent_actions = {name: agent.history for name, agent in self.game.agents.items()} + self.io.write_agent_log(agent_actions=all_agent_actions, episode=self.episode_counter) diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py index 8bbc1b07..2901457f 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -87,7 +87,7 @@ class PrimaiteIO: """Return the path where agent actions will be saved.""" return self.session_path / "agent_actions" / f"episode_{episode}.json" - def write_agent_actions(self, agent_actions: Dict[str, List], episode: int) -> None: + def write_agent_log(self, agent_actions: Dict[str, List], episode: int) -> None: """Take the contents of the agent action log and write it to a file. :param episode: Episode number diff --git a/src/primaite/session/ray_envs.py b/src/primaite/session/ray_envs.py index 5149a225..6dddde51 100644 --- a/src/primaite/session/ray_envs.py +++ b/src/primaite/session/ray_envs.py @@ -59,8 +59,8 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): _LOGGER.info(f"Resetting environment, episode {self.episode_counter}, " f"avg. reward: {rewards}") if self.io.settings.save_agent_actions: - all_agent_actions = {name: agent.action_history for name, agent in self.game.agents.items()} - self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) + all_agent_actions = {name: agent.history for name, agent in self.game.agents.items()} + self.io.write_agent_log(agent_actions=all_agent_actions, episode=self.episode_counter) self.episode_counter += 1 self.game: PrimaiteGame = PrimaiteGame.from_config(self.episode_scheduler(self.episode_counter)) @@ -138,8 +138,8 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): def close(self): """Close the simulation.""" if self.io.settings.save_agent_actions: - all_agent_actions = {name: agent.action_history for name, agent in self.game.agents.items()} - self.io.write_agent_actions(agent_actions=all_agent_actions, episode=self.episode_counter) + all_agent_actions = {name: agent.history for name, agent in self.game.agents.items()} + self.io.write_agent_log(agent_actions=all_agent_actions, episode=self.episode_counter) class PrimaiteRayEnv(gymnasium.Env): diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index 7c38057e..dff536de 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -1,6 +1,6 @@ import yaml -from primaite.game.agent.interface import AgentActionHistoryItem +from primaite.game.agent.interface import AgentHistoryItem from primaite.game.agent.rewards import GreenAdminDatabaseUnreachablePenalty, WebpageUnavailablePenalty from primaite.game.game import PrimaiteGame from primaite.session.environment import PrimaiteGymEnv @@ -75,7 +75,7 @@ def test_uc2_rewards(game_and_agent): state = game.get_sim_state() reward_value = comp.calculate( state, - last_action_response=AgentActionHistoryItem( + last_action_response=AgentHistoryItem( timestep=0, action="NODE_APPLICATION_EXECUTE", parameters={}, request=["execute"], response=response ), ) @@ -91,7 +91,7 @@ def test_uc2_rewards(game_and_agent): state = game.get_sim_state() reward_value = comp.calculate( state, - last_action_response=AgentActionHistoryItem( + last_action_response=AgentHistoryItem( timestep=0, action="NODE_APPLICATION_EXECUTE", parameters={}, request=["execute"], response=response ), ) From e48b71ea1a1627009c8259b1f44ce570aecf113d Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 31 May 2024 15:25:08 +0100 Subject: [PATCH 3/6] get ray to stop crashing --- pyproject.toml | 2 +- src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb | 4 ++-- src/primaite/notebooks/Training-an-RLLib-Agent.ipynb | 7 +++---- src/primaite/notebooks/Training-an-SB3-Agent.ipynb | 7 +++++-- src/primaite/session/ray_envs.py | 3 ++- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c94a388..d01299be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ license-files = ["LICENSE"] [project.optional-dependencies] rl = [ - "ray[rllib] >= 2.9, < 3", + "ray[rllib] >= 2.20.0, < 3", "tensorflow==2.12.0", "stable-baselines3[extra]==2.1.0", ] diff --git a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb index 61b988c6..5ffb19ad 100644 --- a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb +++ b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb @@ -60,8 +60,8 @@ " policies={'defender_1','defender_2'}, # These names are the same as the agents defined in the example config.\n", " policy_mapping_fn=lambda agent_id, episode, worker, **kw: agent_id,\n", " )\n", - " .environment(env=PrimaiteRayMARLEnv, env_config=cfg)#, disable_env_checking=True)\n", - " .rollouts(num_rollout_workers=0)\n", + " .environment(env=PrimaiteRayMARLEnv, env_config=cfg)\n", + " .env_runners(num_env_runners=0)\n", " .training(train_batch_size=128)\n", " )\n" ] diff --git a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb index 9d458426..fbc5f4c6 100644 --- a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb +++ b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb @@ -19,7 +19,6 @@ "from primaite.config.load import data_manipulation_config_path\n", "\n", "from primaite.session.ray_envs import PrimaiteRayEnv\n", - "from ray.rllib.algorithms import ppo\n", "from ray import air, tune\n", "import ray\n", "from ray.rllib.algorithms.ppo import PPOConfig\n", @@ -52,8 +51,8 @@ "\n", "config = (\n", " PPOConfig()\n", - " .environment(env=PrimaiteRayEnv, env_config=env_config, disable_env_checking=True)\n", - " .rollouts(num_rollout_workers=0)\n", + " .environment(env=PrimaiteRayEnv, env_config=env_config)\n", + " .env_runners(num_env_runners=0)\n", " .training(train_batch_size=128)\n", ")\n" ] @@ -74,7 +73,7 @@ "tune.Tuner(\n", " \"PPO\",\n", " run_config=air.RunConfig(\n", - " stop={\"timesteps_total\": 5 * 128}\n", + " stop={\"timesteps_total\": 512}\n", " ),\n", " param_space=config\n", ").fit()\n" diff --git a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb index 9faf5820..1e247e81 100644 --- a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb +++ b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb @@ -43,7 +43,10 @@ "outputs": [], "source": [ "with open(data_manipulation_config_path(), 'r') as f:\n", - " cfg = yaml.safe_load(f)" + " cfg = yaml.safe_load(f)\n", + "for agent in cfg['agents']:\n", + " if agent['ref'] == 'defender':\n", + " agent['agent_settings']['flatten_obs']=True" ] }, { @@ -177,7 +180,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/src/primaite/session/ray_envs.py b/src/primaite/session/ray_envs.py index 6dddde51..111baf84 100644 --- a/src/primaite/session/ray_envs.py +++ b/src/primaite/session/ray_envs.py @@ -45,7 +45,8 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): self.action_space = gymnasium.spaces.Dict( {name: agent.action_manager.space for name, agent in self.agents.items()} ) - + self._obs_space_in_preferred_format = True + self._action_space_in_preferred_format = True super().__init__() @property From 4ee29d129dcb4fb1aaf0cdd6513730131e170653 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 31 May 2024 15:41:40 +0100 Subject: [PATCH 4/6] Fix firewall diagram --- docs/_static/firewall_acl.png | Bin 36036 -> 23963 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/_static/firewall_acl.png b/docs/_static/firewall_acl.png index 1cdd25263cf0817dde59ae02124ab17879197237..1e59657519d0a68acfee06bfd129fae99800acc2 100644 GIT binary patch literal 23963 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 literal 36036 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 Date: Fri, 31 May 2024 15:56:27 +0100 Subject: [PATCH 5/6] remove tests from api docs --- docs/api.rst | 1 - docs/index.rst | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 13f3a1ec..e74be627 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -19,4 +19,3 @@ :recursive: primaite - tests diff --git a/docs/index.rst b/docs/index.rst index a0f302e9..8e7defb1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -125,7 +125,6 @@ Head over to the :ref:`getting-started` page to install and setup PrimAITE! source/state_system source/request_system PrimAITE API - PrimAITE Tests .. toctree:: From f9cff4285676063c1f6457056fcb7f783a734f85 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 31 May 2024 16:03:47 +0100 Subject: [PATCH 6/6] glossary fact check --- docs/source/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 67fd7aaa..c322caac 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -78,4 +78,4 @@ Glossary PrimAITE uses the Gymnasium reinforcement learning framework API to create a training environment and interface with RL agents. Gymnasium defines a common way of creating observations, actions, and rewards. User app home - PrimAITE supports upgrading software version while retaining user data. The user data directory is where configs, notebooks, and results are stored, this location is `~/primaite` on linux/darwin and `C:\\Users\\\\primaite\\` on Windows. + PrimAITE supports upgrading software version while retaining user data. The user data directory is where configs, notebooks, and results are stored, this location is `~/primaite/` on linux/darwin and `C:\\Users\\\\primaite` on Windows.