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-15 10:08:14 +00:00
|
|
|
from typing import Dict, Tuple
|
2024-10-11 15:00:26 +01:00
|
|
|
|
|
|
|
|
from gymnasium import spaces
|
2025-01-15 10:08:14 +00:00
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
2024-10-11 15:00:26 +01:00
|
|
|
|
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
|
|
|
|
2025-01-15 10:08:14 +00:00
|
|
|
class ActionManager(BaseModel):
|
2024-10-11 15:00:26 +01:00
|
|
|
"""Class which manages the action space for an agent."""
|
|
|
|
|
|
2025-01-15 10:08:14 +00:00
|
|
|
class ConfigSchema(BaseModel):
|
|
|
|
|
"""Config Schema for ActionManager."""
|
2024-10-11 15:00:26 +01:00
|
|
|
|
2025-01-15 10:08:14 +00:00
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
action_map: Dict[int, Tuple[str, Dict]] = {}
|
|
|
|
|
"""Mapping between integer action choices and CAOS actions."""
|
2024-10-11 15:00:26 +01:00
|
|
|
|
2025-01-15 10:08:14 +00:00
|
|
|
@field_validator("action_map", mode="after")
|
|
|
|
|
def consecutive_action_nums(cls, v: Dict) -> Dict:
|
|
|
|
|
"""Make sure all numbers between 0 and N are represented as dict keys in action map."""
|
|
|
|
|
assert all([i in v.keys() for i in range(len(v))])
|
|
|
|
|
|
|
|
|
|
config: ActionManager.ConfigSchema = Field(default_factory=lambda: ActionManager.ConfigSchema())
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def action_map(self) -> Dict[int, Tuple[str, Dict]]:
|
|
|
|
|
"""Convenience method for accessing the action map."""
|
|
|
|
|
return self.config.action_map
|
2024-10-11 15:00:26 +01:00
|
|
|
|
|
|
|
|
def get_action(self, action: int) -> Tuple[str, Dict]:
|
2025-01-15 10:08:14 +00:00
|
|
|
"""
|
|
|
|
|
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 an action identifier, followed by parameters stored in a dictionary.
|
|
|
|
|
"""
|
2024-10-11 15:00:26 +01:00
|
|
|
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"))
|