2025-01-03 09:54:11 +00:00
|
|
|
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
2024-12-11 12:01:07 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import random
|
|
|
|
|
from abc import abstractmethod
|
2025-01-16 15:17:42 +00:00
|
|
|
from typing import Dict, List, Optional, Tuple
|
2024-12-11 12:01:07 +00:00
|
|
|
|
2024-12-16 11:27:14 +00:00
|
|
|
from gymnasium.core import ObsType
|
2025-01-10 14:09:15 +00:00
|
|
|
from pydantic import Field
|
2024-12-16 11:27:14 +00:00
|
|
|
|
2025-01-16 15:17:42 +00:00
|
|
|
from primaite.game.agent.scripted_agents.random_agent import PeriodicAgent
|
2024-12-17 12:50:14 +00:00
|
|
|
|
|
|
|
|
__all__ = "AbstractTAPAgent"
|
2024-12-11 12:01:07 +00:00
|
|
|
|
|
|
|
|
|
2025-01-16 15:17:42 +00:00
|
|
|
class AbstractTAPAgent(PeriodicAgent, identifier="AbstractTAP"):
|
2024-12-11 12:01:07 +00:00
|
|
|
"""Base class for TAP agents to inherit from."""
|
|
|
|
|
|
2025-01-10 14:09:15 +00:00
|
|
|
config: "AbstractTAPAgent.ConfigSchema" = Field(default_factory=lambda: AbstractTAPAgent.ConfigSchema())
|
2024-12-17 10:51:57 +00:00
|
|
|
next_execution_timestep: int = 0
|
2024-12-11 12:01:07 +00:00
|
|
|
|
2025-01-16 15:17:42 +00:00
|
|
|
class AgentSettingsSchema(PeriodicAgent.AgentSettingsSchema):
|
|
|
|
|
"""Schema for the `agent_settings` part of the agent config."""
|
|
|
|
|
|
|
|
|
|
possible_starting_nodes: List[str] = Field(default_factory=list)
|
|
|
|
|
|
|
|
|
|
class ConfigSchema(PeriodicAgent.ConfigSchema):
|
2024-12-11 12:01:07 +00:00
|
|
|
"""Configuration schema for Abstract TAP agents."""
|
|
|
|
|
|
2025-01-10 14:39:03 +00:00
|
|
|
type: str = "AbstractTAP"
|
2025-01-16 15:17:42 +00:00
|
|
|
agent_settings: AbstractTAPAgent.AgentSettingsSchema = Field(
|
|
|
|
|
default_factory=lambda: AbstractTAPAgent.AgentSettingsSchema()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
starting_node: Optional[str] = None
|
2024-12-16 11:27:14 +00:00
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def get_action(self, obs: ObsType, timestep: int = 0) -> Tuple[str, Dict]:
|
|
|
|
|
"""Return an action to be taken in the environment."""
|
|
|
|
|
return super().get_action(obs=obs, timestep=timestep)
|
2024-12-11 12:01:07 +00:00
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def setup_agent(self) -> None:
|
|
|
|
|
"""Set up agent."""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def _set_next_execution_timestep(self, timestep: int) -> None:
|
|
|
|
|
"""Set the next execution timestep with a configured random variance.
|
|
|
|
|
|
|
|
|
|
:param timestep: The timestep to add variance to.
|
|
|
|
|
"""
|
2025-01-16 15:17:42 +00:00
|
|
|
random_timestep_increment = random.randint(
|
|
|
|
|
-self.config.agent_settings.variance, self.config.agent_settings.variance
|
|
|
|
|
)
|
2024-12-17 10:51:57 +00:00
|
|
|
self.next_execution_timestep = timestep + random_timestep_increment
|
2024-12-11 12:01:07 +00:00
|
|
|
|
|
|
|
|
def _select_start_node(self) -> None:
|
|
|
|
|
"""Set the starting starting node of the agent to be a random node from this agent's action manager."""
|
|
|
|
|
# we are assuming that every node in the node manager has a data manipulation application at idx 0
|
2025-01-16 15:17:42 +00:00
|
|
|
self.starting_node = random.choice(self.config.agent_settings.possible_starting_nodes)
|
|
|
|
|
self.logger.debug(f"Selected starting node: {self.starting_node}")
|