Merged PR 307: #2369: commiting work done so far
## Summary Added a page to explain how to use the Jupyter notebooks via jupyter command and VSCode Also added Nick's suggestion to fix #2226 Going to be honest - not my finest pull request ## Test process *How have you tested this (if applicable)?* ## Checklist - [X] PR is linked to a **work item** - [X] **acceptance criteria** of linked ticket are met - [X] performed **self-review** of the code - [ ] written **tests** for any new functionality added with this PR - [X] updated the **documentation** if this PR changes or adds functionality - [ ] written/updated **design docs** if this PR implements new functionality - [ ] updated the **change log** - [X] ran **pre-commit** checks for code style - [ ] attended to any **TO-DOs** left in the code #2369: commiting work done so far Related work items: #2369
This commit is contained in:
BIN
docs/_static/notebooks/extensions.png
vendored
Normal file
BIN
docs/_static/notebooks/extensions.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/_static/notebooks/install_extensions.png
vendored
Normal file
BIN
docs/_static/notebooks/install_extensions.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
19
docs/conf.py
19
docs/conf.py
@@ -10,6 +10,7 @@ import datetime
|
|||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import furo # noqa
|
import furo # noqa
|
||||||
|
|
||||||
@@ -63,3 +64,21 @@ html_theme = "furo"
|
|||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
html_theme_options = {"globaltoc_collapse": True, "globaltoc_maxdepth": 2}
|
html_theme_options = {"globaltoc_collapse": True, "globaltoc_maxdepth": 2}
|
||||||
html_copy_source = False
|
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)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ Head over to the :ref:`getting-started` page to install and setup PrimAITE!
|
|||||||
|
|
||||||
source/getting_started
|
source/getting_started
|
||||||
source/primaite_session
|
source/primaite_session
|
||||||
|
source/example_notebooks
|
||||||
source/simulation
|
source/simulation
|
||||||
source/game_layer
|
source/game_layer
|
||||||
source/config
|
source/config
|
||||||
|
|||||||
77
docs/source/example_notebooks.rst
Normal file
77
docs/source/example_notebooks.rst
Normal file
@@ -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 <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.
|
||||||
@@ -11,7 +11,7 @@ Getting Started
|
|||||||
|
|
||||||
Pre-Requisites
|
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
|
.. 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.
|
**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
|
Install PrimAITE
|
||||||
****************
|
****************
|
||||||
|
|
||||||
@@ -38,12 +40,12 @@ Install PrimAITE
|
|||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
:caption: Unix
|
:caption: Unix
|
||||||
|
|
||||||
mkdir ~/primaite/3.0.0
|
mkdir -p ~/primaite/{VERSION}
|
||||||
|
|
||||||
.. code-block:: powershell
|
.. code-block:: powershell
|
||||||
:caption: Windows (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)
|
2. Navigate to the primaite directory and create a new python virtual environment (venv)
|
||||||
@@ -51,13 +53,13 @@ Install PrimAITE
|
|||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
:caption: Unix
|
:caption: Unix
|
||||||
|
|
||||||
cd ~/primaite/3.0.0
|
cd ~/primaite/{VERSION}
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
|
|
||||||
.. code-block:: powershell
|
.. code-block:: powershell
|
||||||
:caption: Windows (Powershell)
|
:caption: Windows (Powershell)
|
||||||
|
|
||||||
cd ~\primaite\3.0.0
|
cd ~\primaite\{VERSION}
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
attrib +h .venv /s /d # Hides the .venv directory
|
attrib +h .venv /s /d # Hides the .venv directory
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Outputs
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
Running a session creates a session output directory in your user data folder. The filepath looks like this:
|
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
|
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
|
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_<n>/step_metadata/step_<n>.json``
|
``~/primaite/{VERSION}/sessions/YYYY-MM-DD/HH-MM-SS/simulation_output/episode_<n>/step_metadata/step_<n>.json``
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ training_config:
|
|||||||
|
|
||||||
|
|
||||||
io_settings:
|
io_settings:
|
||||||
save_checkpoints: true
|
|
||||||
checkpoint_interval: 5
|
|
||||||
save_agent_actions: true
|
save_agent_actions: true
|
||||||
save_step_metadata: false
|
save_step_metadata: false
|
||||||
save_pcap_logs: false
|
save_pcap_logs: false
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class DatabaseFileIntegrity(AbstractReward):
|
|||||||
"""
|
"""
|
||||||
database_file_state = access_from_nested_dict(state, self.location_in_state)
|
database_file_state = access_from_nested_dict(state, self.location_in_state)
|
||||||
if database_file_state is NOT_PRESENT_IN_STATE:
|
if database_file_state is NOT_PRESENT_IN_STATE:
|
||||||
_LOGGER.info(
|
_LOGGER.debug(
|
||||||
f"Could not calculate {self.__class__} reward because "
|
f"Could not calculate {self.__class__} reward because "
|
||||||
"simulation state did not contain enough information."
|
"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
|
# 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)
|
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:
|
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",
|
"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",
|
f"{self._node} was not reported in the simulation state. Returning 0.0",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"# Imports\n",
|
"# Imports\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from primaite.config.load import data_manipulation_config_path\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",
|
"from primaite.session.environment import PrimaiteGymEnv\n",
|
||||||
"import yaml\n",
|
"import yaml\n",
|
||||||
"from pprint import pprint"
|
"from pprint import pprint"
|
||||||
@@ -62,12 +63,12 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"def friendly_output_red_action(info):\n",
|
"def friendly_output_red_action(info):\n",
|
||||||
" # parse the info dict form step output and write out what the red agent is doing\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_info : AgentActionHistoryItem = info['agent_actions']['data_manipulation_attacker']\n",
|
||||||
" red_action = red_info[0]\n",
|
" red_action = red_info.action\n",
|
||||||
" if red_action == 'DONOTHING':\n",
|
" if red_action == 'DONOTHING':\n",
|
||||||
" red_str = 'DO NOTHING'\n",
|
" red_str = 'DO NOTHING'\n",
|
||||||
" elif red_action == 'NODE_APPLICATION_EXECUTE':\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",
|
" red_str = f\"ATTACK from {client}\"\n",
|
||||||
" return red_str"
|
" return red_str"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -576,7 +576,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"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",
|
" tries += 1\n",
|
||||||
" obs, reward, terminated, truncated, info = env.step(0)\n",
|
" obs, reward, terminated, truncated, info = env.step(0)\n",
|
||||||
"\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",
|
" # client 1 has NMNEs, let's block it\n",
|
||||||
" obs, reward, terminated, truncated, info = env.step(50) # block client 1\n",
|
" obs, reward, terminated, truncated, info = env.step(50) # block client 1\n",
|
||||||
" print(\"blocking client 1\")\n",
|
" print(\"blocking client 1\")\n",
|
||||||
" break\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",
|
" # client 2 has NMNEs, so let's block it\n",
|
||||||
" obs, reward, terminated, truncated, info = env.step(51) # block client 2\n",
|
" obs, reward, terminated, truncated, info = env.step(51) # block client 2\n",
|
||||||
" print(\"blocking client 2\")\n",
|
" print(\"blocking client 2\")\n",
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class WirelessNetworkInterface(NetworkInterface, ABC):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not self._connected_node:
|
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
|
return
|
||||||
|
|
||||||
if self._connected_node.operating_state != NodeOperatingState.ON:
|
if self._connected_node.operating_state != NodeOperatingState.ON:
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class WiredNetworkInterface(NetworkInterface, ABC):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if not self._connected_node:
|
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
|
return False
|
||||||
|
|
||||||
if self._connected_node.operating_state != NodeOperatingState.ON:
|
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.
|
:param link: The Link instance to connect to this network interface.
|
||||||
"""
|
"""
|
||||||
if self._connected_link:
|
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
|
return
|
||||||
|
|
||||||
if self._connected_link == link:
|
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
|
return
|
||||||
|
|
||||||
self._connected_link = link
|
self._connected_link = link
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class Application(IOSoftware):
|
|||||||
|
|
||||||
if self.operating_state is not self.operating_state.RUNNING:
|
if self.operating_state is not self.operating_state.RUNNING:
|
||||||
# service is not 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 False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class Service(IOSoftware):
|
|||||||
|
|
||||||
if self.operating_state is not ServiceOperatingState.RUNNING:
|
if self.operating_state is not ServiceOperatingState.RUNNING:
|
||||||
# service is not 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 False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user