diff --git a/docs/_static/notebooks/extensions.png b/docs/_static/notebooks/extensions.png new file mode 100644 index 00000000..8441802d Binary files /dev/null and b/docs/_static/notebooks/extensions.png differ diff --git a/docs/_static/notebooks/install_extensions.png b/docs/_static/notebooks/install_extensions.png new file mode 100644 index 00000000..db026ce3 Binary files /dev/null and b/docs/_static/notebooks/install_extensions.png differ diff --git a/docs/conf.py b/docs/conf.py index d246afe5..a666e460 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,6 +10,7 @@ import datetime # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information import os import sys +from typing import Any import furo # noqa @@ -63,3 +64,21 @@ html_theme = "furo" html_static_path = ["_static"] html_theme_options = {"globaltoc_collapse": True, "globaltoc_maxdepth": 2} html_copy_source = False + + +def replace_token(app: Any, docname: Any, source: Any): + """Replaces a token from the list of tokens.""" + result = source[0] + for key in app.config.tokens: + result = result.replace(key, app.config.tokens[key]) + source[0] = result + + +tokens = {"{VERSION}": release} # Token VERSION is replaced by the value of the PrimAITE version in the version file +"""Dict containing the tokens that need to be replaced in documentation.""" + + +def setup(app: Any): + """Custom setup for sphinx.""" + app.add_config_value("tokens", {}, True) + app.connect("source-read", replace_token) diff --git a/docs/index.rst b/docs/index.rst index cf17b1c5..4cc81b13 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -105,6 +105,7 @@ Head over to the :ref:`getting-started` page to install and setup PrimAITE! source/getting_started source/primaite_session + source/example_notebooks source/simulation source/game_layer source/config diff --git a/docs/source/example_notebooks.rst b/docs/source/example_notebooks.rst new file mode 100644 index 00000000..99d47822 --- /dev/null +++ b/docs/source/example_notebooks.rst @@ -0,0 +1,77 @@ +.. only:: comment + + © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK + +Example Jupyter Notebooks +========================= + +There are a few example notebooks included which help with the understanding of PrimAITE's capabilities. + +The Jupyter Notebooks can be run via the 2 examples below. These assume that the instructions to install PrimAITE from the :ref:`Getting Started ` page is completed as a prerequisite. + +Running Jupyter Notebooks +------------------------- + +1. Navigate to the PrimAITE directory + +.. code-block:: bash + :caption: Unix + + cd ~/primaite/{VERSION} + +.. code-block:: powershell + :caption: Windows (Powershell) + + cd ~\primaite\{VERSION} + +2. Run jupyter notebook (the python environment to which you installed PrimAITE must be active) + +.. code-block:: bash + :caption: Unix + + jupyter notebook + +.. code-block:: powershell + :caption: Windows (Powershell) + + jupyter notebook + +3. Opening the jupyter webpage (optional) + +The default web browser may automatically open the webpage. However, if that is not the case, click the link shown in your command prompt output. It should look like this: ``http://localhost:8888/?token=0123456798abc0123456789abc`` + + +4. Navigate to the list of notebooks + +The example notebooks are located in ``notebooks/example_notebooks/``. The file system shown in the jupyter webpage is relative to the location in which the ``jupyter notebook`` command was used. + + +Running Jupyter Notebooks via VSCode +------------------------------------ + +It is also possible to view the Jupyter notebooks within VSCode. + +The best place to start is by opening a notebook file (.ipynb) in VSCode. If using VSCode to view a notebook for the first time, follow the steps below. + +Installing extensions +""""""""""""""""""""" + +VSCode may need some extensions to be installed if not already done. +To do this, press the "Select Kernel" button on the top right. + +This should open a dialog which has the option to install python and jupyter extensions. + +.. image:: ../../_static/notebooks/install_extensions.png + :width: 700 + :align: center + :alt: :: The top dialog option that appears will automatically install the extensions + +The following extensions should now be installed + +.. image:: ../../_static/notebooks/extensions.png + :width: 300 + :align: center + +VSCode will then ask for a Python environment version to use. PrimAITE is compatible with Python versions 3.8 - 3.10 + +You should now be able to interact with the notebook. diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index a800ee56..bb6e0019 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -11,7 +11,7 @@ Getting Started Pre-Requisites -In order to get **PrimAITE** installed, you will need to have a python version between 3.8 and 3.11 installed. If you don't already have it, this is how to install it: +In order to get **PrimAITE** installed, you will need Python, venv, and pip. If you don't already have them, this is how to install it: .. code-block:: bash @@ -30,6 +30,8 @@ In order to get **PrimAITE** installed, you will need to have a python version b **PrimAITE** is designed to be OS-agnostic, and thus should work on most variations/distros of Linux, Windows, and MacOS. +Installing PrimAITE has been tested with all supported python versions, venv 20.24.1, and pip 23. + Install PrimAITE **************** @@ -38,12 +40,12 @@ Install PrimAITE .. code-block:: bash :caption: Unix - mkdir ~/primaite/3.0.0 + mkdir -p ~/primaite/{VERSION} .. code-block:: powershell :caption: Windows (Powershell) - mkdir ~\primaite\3.0.0 + mkdir ~\primaite\{VERSION} 2. Navigate to the primaite directory and create a new python virtual environment (venv) @@ -51,13 +53,13 @@ Install PrimAITE .. code-block:: bash :caption: Unix - cd ~/primaite/3.0.0 + cd ~/primaite/{VERSION} python3 -m venv .venv .. code-block:: powershell :caption: Windows (Powershell) - cd ~\primaite\3.0.0 + cd ~\primaite\{VERSION} python3 -m venv .venv attrib +h .venv /s /d # Hides the .venv directory diff --git a/docs/source/primaite_session.rst b/docs/source/primaite_session.rst index 87a3f03d..d0caeaad 100644 --- a/docs/source/primaite_session.rst +++ b/docs/source/primaite_session.rst @@ -35,7 +35,7 @@ Outputs ------- Running a session creates a session output directory in your user data folder. The filepath looks like this: -``~/primaite/3.0.0/sessions/YYYY-MM-DD/HH-MM-SS/``. This folder contains the simulation sys logs generated by each node, +``~/primaite/{VERSION}/sessions/YYYY-MM-DD/HH-MM-SS/``. This folder contains the simulation sys logs generated by each node, the saved agent checkpoints, and final model. The folder also contains a .json file for each episode step that contains the action, reward, and simulation state. These can be found in -``~/primaite/3.0.0/sessions/YYYY-MM-DD/HH-MM-SS/simulation_output/episode_/step_metadata/step_.json`` +``~/primaite/{VERSION}/sessions/YYYY-MM-DD/HH-MM-SS/simulation_output/episode_/step_metadata/step_.json`` diff --git a/src/primaite/config/_package_data/data_manipulation_marl.yaml b/src/primaite/config/_package_data/data_manipulation_marl.yaml index e4c93161..85d282ba 100644 --- a/src/primaite/config/_package_data/data_manipulation_marl.yaml +++ b/src/primaite/config/_package_data/data_manipulation_marl.yaml @@ -13,8 +13,6 @@ training_config: io_settings: - save_checkpoints: true - checkpoint_interval: 5 save_agent_actions: true save_step_metadata: false save_pcap_logs: false diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 52bed9e2..2201b09e 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -111,7 +111,7 @@ class DatabaseFileIntegrity(AbstractReward): """ database_file_state = access_from_nested_dict(state, self.location_in_state) if database_file_state is NOT_PRESENT_IN_STATE: - _LOGGER.info( + _LOGGER.debug( f"Could not calculate {self.__class__} reward because " "simulation state did not contain enough information." ) @@ -231,7 +231,7 @@ class WebpageUnavailablePenalty(AbstractReward): # If the last request did actually go through, then check if the webpage also loaded web_browser_state = access_from_nested_dict(state, self.location_in_state) if web_browser_state is NOT_PRESENT_IN_STATE or "history" not in web_browser_state: - _LOGGER.info( + _LOGGER.debug( "Web browser reward could not be calculated because the web browser history on node", f"{self._node} was not reported in the simulation state. Returning 0.0", ) diff --git a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb index 779d89f6..56e9bf5a 100644 --- a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb @@ -22,6 +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.session.environment import PrimaiteGymEnv\n", "import yaml\n", "from pprint import pprint" @@ -62,12 +63,12 @@ "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 = info['agent_actions']['data_manipulation_attacker']\n", - " red_action = red_info[0]\n", + " red_info : AgentActionHistoryItem = info['agent_actions']['data_manipulation_attacker']\n", + " red_action = red_info.action\n", " if red_action == 'DONOTHING':\n", " red_str = 'DO NOTHING'\n", " elif red_action == 'NODE_APPLICATION_EXECUTE':\n", - " client = \"client 1\" if red_info[1]['node_id'] == 0 else \"client 2\"\n", + " client = \"client 1\" if red_info.parameters['node_id'] == 0 else \"client 2\"\n", " red_str = f\"ATTACK from {client}\"\n", " return red_str" ] diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb index 946202b6..7ec58b2c 100644 --- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb @@ -576,7 +576,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, even though the red agent executes an attack, the reward stays at 0.8." + "Now, even though the red agent executes an attack, the reward will stay at 0.8." ] }, { @@ -616,12 +616,12 @@ " tries += 1\n", " obs, reward, terminated, truncated, info = env.step(0)\n", "\n", - " if obs['NODES'][6]['NETWORK_INTERFACES'][1]['nmne']['outbound'] == 1:\n", + " if obs['NODES'][6]['NICS'][1]['NMNE']['outbound'] == 1:\n", " # client 1 has NMNEs, let's block it\n", " obs, reward, terminated, truncated, info = env.step(50) # block client 1\n", " print(\"blocking client 1\")\n", " break\n", - " elif obs['NODES'][7]['NETWORK_INTERFACES'][1]['nmne']['outbound'] == 1:\n", + " elif obs['NODES'][7]['NICS'][1]['NMNE']['outbound'] == 1:\n", " # client 2 has NMNEs, so let's block it\n", " obs, reward, terminated, truncated, info = env.step(51) # block client 2\n", " print(\"blocking client 2\")\n", diff --git a/src/primaite/simulator/network/airspace.py b/src/primaite/simulator/network/airspace.py index 5ceedc8e..a8343675 100644 --- a/src/primaite/simulator/network/airspace.py +++ b/src/primaite/simulator/network/airspace.py @@ -157,7 +157,7 @@ class WirelessNetworkInterface(NetworkInterface, ABC): return if not self._connected_node: - _LOGGER.error(f"Interface {self} cannot be enabled as it is not connected to a Node") + _LOGGER.warning(f"Interface {self} cannot be enabled as it is not connected to a Node") return if self._connected_node.operating_state != NodeOperatingState.ON: diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 3d8640a6..0cad4124 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -297,7 +297,7 @@ class WiredNetworkInterface(NetworkInterface, ABC): return True if not self._connected_node: - _LOGGER.error(f"Interface {self} cannot be enabled as it is not connected to a Node") + _LOGGER.warning(f"Interface {self} cannot be enabled as it is not connected to a Node") return False if self._connected_node.operating_state != NodeOperatingState.ON: @@ -343,11 +343,11 @@ class WiredNetworkInterface(NetworkInterface, ABC): :param link: The Link instance to connect to this network interface. """ if self._connected_link: - _LOGGER.error(f"Cannot connect Link to network interface {self} as it already has a connection") + _LOGGER.warning(f"Cannot connect Link to network interface {self} as it already has a connection") return if self._connected_link == link: - _LOGGER.error(f"Cannot connect Link to network interface {self} as it is already connected") + _LOGGER.warning(f"Cannot connect Link to network interface {self} as it is already connected") return self._connected_link = link diff --git a/src/primaite/simulator/system/applications/application.py b/src/primaite/simulator/system/applications/application.py index 74013681..b7422680 100644 --- a/src/primaite/simulator/system/applications/application.py +++ b/src/primaite/simulator/system/applications/application.py @@ -83,7 +83,7 @@ class Application(IOSoftware): if self.operating_state is not self.operating_state.RUNNING: # service is not running - _LOGGER.error(f"Cannot perform action: {self.name} is {self.operating_state.name}") + _LOGGER.debug(f"Cannot perform action: {self.name} is {self.operating_state.name}") return False return True diff --git a/src/primaite/simulator/system/services/service.py b/src/primaite/simulator/system/services/service.py index e15377a9..b2a6f685 100644 --- a/src/primaite/simulator/system/services/service.py +++ b/src/primaite/simulator/system/services/service.py @@ -59,7 +59,7 @@ class Service(IOSoftware): if self.operating_state is not ServiceOperatingState.RUNNING: # service is not running - _LOGGER.error(f"Cannot perform action: {self.name} is {self.operating_state.name}") + _LOGGER.debug(f"Cannot perform action: {self.name} is {self.operating_state.name}") return False return True