2025-01-03 14:53:54 +00:00
|
|
|
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
2024-10-21 17:51:55 +01:00
|
|
|
"""yaml example.
|
2024-10-11 15:00:26 +01:00
|
|
|
|
|
|
|
|
agents:
|
|
|
|
|
- name: agent_1
|
|
|
|
|
action_space:
|
|
|
|
|
actions:
|
|
|
|
|
- do_nothing
|
|
|
|
|
- node_service_start
|
|
|
|
|
- node_service_stop
|
|
|
|
|
action_map:
|
|
|
|
|
"""
|
|
|
|
|
|
2024-10-17 12:22:30 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2025-01-14 13:48:18 +00:00
|
|
|
from typing import Dict, Optional, Tuple
|
2024-10-11 15:00:26 +01:00
|
|
|
|
|
|
|
|
from gymnasium import spaces
|
|
|
|
|
|
2024-10-21 17:51:55 +01:00
|
|
|
# from primaite.game.game import PrimaiteGame # TODO: Breaks things
|
2024-10-23 18:45:57 +01:00
|
|
|
from primaite.game.agent.actions.abstract import AbstractAction
|
2024-10-17 12:22:30 +01:00
|
|
|
from primaite.interface.request import RequestFormat
|
2024-10-11 15:00:26 +01:00
|
|
|
|
2024-11-15 17:47:58 +00:00
|
|
|
__all__ = ("DoNothingAction", "ActionManager")
|
2024-11-12 14:49:44 +00:00
|
|
|
|
2024-10-11 15:00:26 +01:00
|
|
|
|
2024-10-21 17:51:55 +01:00
|
|
|
class DoNothingAction(AbstractAction, identifier="do_nothing"):
|
|
|
|
|
"""Do Nothing Action."""
|
|
|
|
|
|
2024-10-11 15:00:26 +01:00
|
|
|
class ConfigSchema(AbstractAction.ConfigSchema):
|
2024-12-12 16:08:11 +00:00
|
|
|
"""Configuration Schema for do_nothingAction."""
|
2024-10-21 17:51:55 +01:00
|
|
|
|
2024-11-19 18:57:40 +00:00
|
|
|
type: str = "do_nothing"
|
2024-10-11 15:00:26 +01:00
|
|
|
|
2024-10-30 18:34:05 +00:00
|
|
|
@classmethod
|
|
|
|
|
def form_request(cls, config: ConfigSchema) -> RequestFormat:
|
2024-10-17 16:59:44 +01:00
|
|
|
"""Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
|
2024-10-11 15:00:26 +01:00
|
|
|
return ["do_nothing"]
|
|
|
|
|
|
2024-10-17 12:22:30 +01:00
|
|
|
|
2024-10-11 15:00:26 +01:00
|
|
|
class ActionManager:
|
|
|
|
|
"""Class which manages the action space for an agent."""
|
|
|
|
|
|
2025-01-14 13:48:18 +00:00
|
|
|
def __init__(self, act_map: Optional[Dict[int, Dict]] = None) -> None:
|
2024-10-11 15:00:26 +01:00
|
|
|
"""Init method for ActionManager.
|
|
|
|
|
|
|
|
|
|
:param act_map: Action map which maps integers to actions. Used for restricting the set of possible actions.
|
|
|
|
|
:type act_map: Optional[Dict[int, Dict]]
|
|
|
|
|
"""
|
|
|
|
|
self.action_map: Dict[int, Tuple[str, Dict]] = {}
|
|
|
|
|
"""
|
|
|
|
|
Action mapping that converts an integer to a specific action and parameter choice.
|
|
|
|
|
|
|
|
|
|
For example :
|
2024-11-12 14:49:44 +00:00
|
|
|
{0: ("node_service_scan", {node_name:"client_1", service_name:"WebBrowser"})}
|
2024-10-11 15:00:26 +01:00
|
|
|
"""
|
2025-01-14 13:48:18 +00:00
|
|
|
# allows restricting set of possible actions - TODO: Refactor to be a list?
|
2024-10-11 15:00:26 +01:00
|
|
|
if act_map is None:
|
|
|
|
|
# raise RuntimeError("Action map must be specified in the config file.")
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()}
|
|
|
|
|
# make sure all numbers between 0 and N are represented as dict keys in action map
|
|
|
|
|
assert all([i in self.action_map.keys() for i in range(len(self.action_map))])
|
|
|
|
|
|
|
|
|
|
def get_action(self, action: int) -> Tuple[str, Dict]:
|
|
|
|
|
"""Produce action in CAOS format."""
|
|
|
|
|
"""the agent chooses an action (as an integer), this is converted into an action in CAOS format"""
|
|
|
|
|
"""The CAOS format is basically a action identifier, followed by parameters stored in a dictionary"""
|
|
|
|
|
act_identifier, act_options = self.action_map[action]
|
|
|
|
|
return act_identifier, act_options
|
|
|
|
|
|
|
|
|
|
def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat:
|
|
|
|
|
"""Take action in CAOS format and use the execution definition to change it into PrimAITE request format."""
|
2024-12-06 15:12:31 +00:00
|
|
|
act_class = AbstractAction._registry[action_identifier]
|
|
|
|
|
config = act_class.ConfigSchema(**action_options)
|
|
|
|
|
return act_class.form_request(config=config)
|
2024-10-11 15:00:26 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def space(self) -> spaces.Space:
|
|
|
|
|
"""Return the gymnasium action space for this agent."""
|
|
|
|
|
return spaces.Discrete(len(self.action_map))
|
2024-11-19 18:58:51 +00:00
|
|
|
|
2024-10-11 15:00:26 +01:00
|
|
|
@classmethod
|
2025-01-14 13:48:18 +00:00
|
|
|
def from_config(cls, cfg: Dict) -> "ActionManager":
|
2024-10-11 15:00:26 +01:00
|
|
|
"""
|
2025-01-14 13:48:18 +00:00
|
|
|
Construct an ActionManager from a config dictionary.
|
|
|
|
|
|
|
|
|
|
The action space config supports must contain the following key:
|
|
|
|
|
``action_map`` - List of actions available to the agent, formatted as a dictionary where the key is the
|
|
|
|
|
action number between 0 - N, and the value is the CAOS-formatted action.
|
|
|
|
|
|
2024-10-11 15:00:26 +01:00
|
|
|
:param cfg: The action space config.
|
|
|
|
|
:type cfg: Dict
|
|
|
|
|
:return: The constructed ActionManager.
|
|
|
|
|
:rtype: ActionManager
|
|
|
|
|
"""
|
2025-01-14 13:48:18 +00:00
|
|
|
return cls(**cfg.get("options", {}), act_map=cfg.get("action_map"))
|