diff --git a/.gitignore b/.gitignore
index c3d54ada..2ba8d4a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,3 +164,7 @@ src/primaite/notebooks/scratch.py
sandbox.py
sandbox/
sandbox.ipynb
+
+# benchmarking
+**/benchmark/sessions/
+**/benchmark/output/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 227cf729..17bf3557 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for SQL INSERT command.
- Added ability to log each agent's action choices in each step to a JSON file.
- Removal of Link bandwidth hardcoding. This can now be configured via the network configuraiton yaml. Will default to 100 if not present.
+- Added NMAP application to all host and layer-3 network nodes.
### Bug Fixes
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
new file mode 100644
index 00000000..4ad398b9
--- /dev/null
+++ b/benchmark/benchmark.py
@@ -0,0 +1,22 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+from typing import Any, Dict, Optional, Tuple
+
+from gymnasium.core import ObsType
+
+from primaite.session.environment import PrimaiteGymEnv
+
+
+class BenchmarkPrimaiteGymEnv(PrimaiteGymEnv):
+ """
+ Class that extends the PrimaiteGymEnv.
+
+ The reset method is extended so that the average rewards per episode are recorded.
+ """
+
+ total_time_steps: int = 0
+
+ def reset(self, seed: Optional[int] = None) -> Tuple[ObsType, Dict[str, Any]]:
+ """Overrides the PrimAITEGymEnv reset so that the total timesteps is saved."""
+ self.total_time_steps += self.game.step_counter
+
+ return super().reset(seed=seed)
diff --git a/benchmark/primaite_benchmark.py b/benchmark/primaite_benchmark.py
index 3f179037..bcac5f8a 100644
--- a/benchmark/primaite_benchmark.py
+++ b/benchmark/primaite_benchmark.py
@@ -3,210 +3,95 @@
raise DeprecationWarning(
"Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet."
)
-# © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
import json
-import platform
import shutil
-import sys
from datetime import datetime
from pathlib import Path
-from typing import Any, Dict, Final, Optional, Tuple, Union
-from unittest.mock import patch
+from typing import Any, Dict, Final, Tuple
-import GPUtil
-import plotly.graph_objects as go
-import polars as pl
-import psutil
-import yaml
-from plotly.graph_objs import Figure
-from pylatex import Command, Document
-from pylatex import Figure as LatexFigure
-from pylatex import Section, Subsection, Tabular
-from pylatex.utils import bold
+from report import build_benchmark_latex_report
+from stable_baselines3 import PPO
import primaite
-from primaite.config.lay_down_config import data_manipulation_config_path
-from primaite.data_viz.session_plots import get_plotly_config
-from primaite.environment.primaite_env import Primaite
-from primaite.primaite_session import PrimaiteSession
+from benchmark import BenchmarkPrimaiteGymEnv
+from primaite.config.load import data_manipulation_config_path
_LOGGER = primaite.getLogger(__name__)
+_MAJOR_V = primaite.__version__.split(".")[0]
+
_BENCHMARK_ROOT = Path(__file__).parent
-_RESULTS_ROOT: Final[Path] = _BENCHMARK_ROOT / "results"
-_RESULTS_ROOT.mkdir(exist_ok=True, parents=True)
+_RESULTS_ROOT: Final[Path] = _BENCHMARK_ROOT / "results" / f"v{_MAJOR_V}"
+_VERSION_ROOT: Final[Path] = _RESULTS_ROOT / f"v{primaite.__version__}"
+_SESSION_METADATA_ROOT: Final[Path] = _VERSION_ROOT / "session_metadata"
-_OUTPUT_ROOT: Final[Path] = _BENCHMARK_ROOT / "output"
-# Clear and recreate the output directory
-if _OUTPUT_ROOT.exists():
- shutil.rmtree(_OUTPUT_ROOT)
-_OUTPUT_ROOT.mkdir()
-
-_TRAINING_CONFIG_PATH = _BENCHMARK_ROOT / "config" / "benchmark_training_config.yaml"
-_LAY_DOWN_CONFIG_PATH = data_manipulation_config_path()
+_SESSION_METADATA_ROOT.mkdir(parents=True, exist_ok=True)
-def get_size(size_bytes: int) -> str:
- """
- Scale bytes to its proper format.
+class BenchmarkSession:
+ """Benchmark Session class."""
- e.g:
- 1253656 => '1.20MB'
- 1253656678 => '1.17GB'
+ gym_env: BenchmarkPrimaiteGymEnv
+ """Gym environment used by the session to train."""
- :
- """
- factor = 1024
- for unit in ["", "K", "M", "G", "T", "P"]:
- if size_bytes < factor:
- return f"{size_bytes:.2f}{unit}B"
- size_bytes /= factor
+ num_episodes: int
+ """Number of episodes to run the training session."""
+ episode_len: int
+ """The number of steps per episode."""
-def _get_system_info() -> Dict:
- """Builds and returns a dict containing system info."""
- uname = platform.uname()
- cpu_freq = psutil.cpu_freq()
- virtual_mem = psutil.virtual_memory()
- swap_mem = psutil.swap_memory()
- gpus = GPUtil.getGPUs()
- return {
- "System": {
- "OS": uname.system,
- "OS Version": uname.version,
- "Machine": uname.machine,
- "Processor": uname.processor,
- },
- "CPU": {
- "Physical Cores": psutil.cpu_count(logical=False),
- "Total Cores": psutil.cpu_count(logical=True),
- "Max Frequency": f"{cpu_freq.max:.2f}Mhz",
- },
- "Memory": {"Total": get_size(virtual_mem.total), "Swap Total": get_size(swap_mem.total)},
- "GPU": [{"Name": gpu.name, "Total Memory": f"{gpu.memoryTotal}MB"} for gpu in gpus],
- }
+ total_steps: int
+ """Number of steps to run the training session."""
+ batch_size: int
+ """Number of steps for each episode."""
-def _build_benchmark_latex_report(
- benchmark_metadata_dict: Dict, this_version_plot_path: Path, all_version_plot_path: Path
-) -> None:
- geometry_options = {"tmargin": "2.5cm", "rmargin": "2.5cm", "bmargin": "2.5cm", "lmargin": "2.5cm"}
- data = benchmark_metadata_dict
- primaite_version = data["primaite_version"]
+ learning_rate: float
+ """Learning rate for the model."""
- # Create a new document
- doc = Document("report", geometry_options=geometry_options)
- # Title
- doc.preamble.append(Command("title", f"PrimAITE {primaite_version} Learning Benchmark"))
- doc.preamble.append(Command("author", "PrimAITE Dev Team"))
- doc.preamble.append(Command("date", datetime.now().date()))
- doc.append(Command("maketitle"))
+ start_time: datetime
+ """Start time for the session."""
- sessions = data["total_sessions"]
- episodes = data["training_config"]["num_train_episodes"]
- steps = data["training_config"]["num_train_steps"]
-
- # Body
- with doc.create(Section("Introduction")):
- doc.append(
- f"PrimAITE v{primaite_version} was benchmarked automatically upon release. Learning rate metrics "
- f"were captured to be referenced during system-level testing and user acceptance testing (UAT)."
- )
- doc.append(
- f"\nThe benchmarking process consists of running {sessions} training session using the same "
- f"training and lay down config files. Each session trains an agent for {episodes} episodes, "
- f"with each episode consisting of {steps} steps."
- )
- doc.append(
- f"\nThe mean reward per episode from each session is captured. This is then used to calculate a "
- f"combined average reward per episode from the {sessions} individual sessions for smoothing. "
- f"Finally, a 25-widow rolling average of the combined average reward per session is calculated for "
- f"further smoothing."
- )
-
- with doc.create(Section("System Information")):
- with doc.create(Subsection("Python")):
- with doc.create(Tabular("|l|l|")) as table:
- table.add_hline()
- table.add_row((bold("Version"), sys.version))
- table.add_hline()
- for section, section_data in data["system_info"].items():
- if section_data:
- with doc.create(Subsection(section)):
- if isinstance(section_data, dict):
- with doc.create(Tabular("|l|l|")) as table:
- table.add_hline()
- for key, value in section_data.items():
- table.add_row((bold(key), value))
- table.add_hline()
- elif isinstance(section_data, list):
- headers = section_data[0].keys()
- tabs_str = "|".join(["l" for _ in range(len(headers))])
- tabs_str = f"|{tabs_str}|"
- with doc.create(Tabular(tabs_str)) as table:
- table.add_hline()
- table.add_row([bold(h) for h in headers])
- table.add_hline()
- for item in section_data:
- table.add_row(item.values())
- table.add_hline()
-
- headers_map = {
- "total_sessions": "Total Sessions",
- "total_episodes": "Total Episodes",
- "total_time_steps": "Total Steps",
- "av_s_per_session": "Av Session Duration (s)",
- "av_s_per_step": "Av Step Duration (s)",
- "av_s_per_100_steps_10_nodes": "Av Duration per 100 Steps per 10 Nodes (s)",
- }
- with doc.create(Section("Stats")):
- with doc.create(Subsection("Benchmark Results")):
- with doc.create(Tabular("|l|l|")) as table:
- table.add_hline()
- for section, header in headers_map.items():
- if section.startswith("av_"):
- table.add_row((bold(header), f"{data[section]:.4f}"))
- else:
- table.add_row((bold(header), data[section]))
- table.add_hline()
-
- with doc.create(Section("Graphs")):
- with doc.create(Subsection(f"PrimAITE {primaite_version} Learning Benchmark Plot")):
- with doc.create(LatexFigure(position="h!")) as pic:
- pic.add_image(str(this_version_plot_path))
- pic.add_caption(f"PrimAITE {primaite_version} Learning Benchmark Plot")
-
- with doc.create(Subsection("PrimAITE All Versions Learning Benchmark Plot")):
- with doc.create(LatexFigure(position="h!")) as pic:
- pic.add_image(str(all_version_plot_path))
- pic.add_caption("PrimAITE All Versions Learning Benchmark Plot")
-
- doc.generate_pdf(str(this_version_plot_path).replace(".png", ""), clean_tex=True)
-
-
-class BenchmarkPrimaiteSession(PrimaiteSession):
- """A benchmarking primaite session."""
+ end_time: datetime
+ """End time for the session."""
def __init__(
self,
- training_config_path: Union[str, Path],
- lay_down_config_path: Union[str, Path],
- ) -> None:
- super().__init__(training_config_path, lay_down_config_path)
- self.setup()
+ gym_env: BenchmarkPrimaiteGymEnv,
+ episode_len: int,
+ num_episodes: int,
+ n_steps: int,
+ batch_size: int,
+ learning_rate: float,
+ ):
+ """Initialise the BenchmarkSession."""
+ self.gym_env = gym_env
+ self.episode_len = episode_len
+ self.n_steps = n_steps
+ self.num_episodes = num_episodes
+ self.total_steps = self.num_episodes * self.episode_len
+ self.batch_size = batch_size
+ self.learning_rate = learning_rate
- @property
- def env(self) -> Primaite:
- """Direct access to the env for ease of testing."""
- return self._agent_session._env # noqa
+ def train(self):
+ """Run the training session."""
+ # start timer for session
+ self.start_time = datetime.now()
+ model = PPO(
+ policy="MlpPolicy",
+ env=self.gym_env,
+ learning_rate=self.learning_rate,
+ n_steps=self.n_steps,
+ batch_size=self.batch_size,
+ verbose=0,
+ tensorboard_log="./PPO_UC2/",
+ )
+ model.learn(total_timesteps=self.total_steps)
- def __enter__(self) -> "BenchmarkPrimaiteSession":
- return self
+ # end timer for session
+ self.end_time = datetime.now()
- # TODO: typehints uncertain
- def __exit__(self, type: Any, value: Any, tb: Any) -> None:
- shutil.rmtree(self.session_path)
- _LOGGER.debug(f"Deleted benchmark session directory: {self.session_path}")
+ self.session_metadata = self.generate_learn_metadata_dict()
def _learn_benchmark_durations(self) -> Tuple[float, float, float]:
"""
@@ -220,235 +105,99 @@ class BenchmarkPrimaiteSession(PrimaiteSession):
:return: The learning benchmark durations as a Tuple of three floats:
Tuple[total_s, s_per_step, s_per_100_steps_10_nodes].
"""
- data = self.metadata_file_as_dict()
- start_dt = datetime.fromisoformat(data["start_datetime"])
- end_dt = datetime.fromisoformat(data["end_datetime"])
- delta = end_dt - start_dt
+ delta = self.end_time - self.start_time
total_s = delta.total_seconds()
- total_steps = data["learning"]["total_time_steps"]
+ total_steps = self.batch_size * self.num_episodes
s_per_step = total_s / total_steps
- num_nodes = self.env.num_nodes
+ num_nodes = len(self.gym_env.game.simulation.network.nodes)
num_intervals = total_steps / 100
av_interval_time = total_s / num_intervals
s_per_100_steps_10_nodes = av_interval_time / (num_nodes / 10)
return total_s, s_per_step, s_per_100_steps_10_nodes
- def learn_metadata_dict(self) -> Dict[str, Any]:
+ def generate_learn_metadata_dict(self) -> Dict[str, Any]:
"""Metadata specific to the learning session."""
total_s, s_per_step, s_per_100_steps_10_nodes = self._learn_benchmark_durations()
+ self.gym_env.average_reward_per_episode.pop(0) # remove episode 0
return {
- "total_episodes": self.env.actual_episode_count,
- "total_time_steps": self.env.total_step_count,
+ "total_episodes": self.gym_env.episode_counter,
+ "total_time_steps": self.gym_env.total_time_steps,
"total_s": total_s,
"s_per_step": s_per_step,
"s_per_100_steps_10_nodes": s_per_100_steps_10_nodes,
- "av_reward_per_episode": self.learn_av_reward_per_episode_dict(),
+ "av_reward_per_episode": self.gym_env.average_reward_per_episode,
}
-def _get_benchmark_session_path(session_timestamp: datetime) -> Path:
- return _OUTPUT_ROOT / session_timestamp.strftime("%Y-%m-%d_%H-%M-%S")
-
-
-def _get_benchmark_primaite_session() -> BenchmarkPrimaiteSession:
- with patch("primaite.agents.agent_abc.get_session_path", _get_benchmark_session_path) as mck:
- mck.session_timestamp = datetime.now()
- return BenchmarkPrimaiteSession(_TRAINING_CONFIG_PATH, _LAY_DOWN_CONFIG_PATH)
-
-
-def _build_benchmark_results_dict(start_datetime: datetime, metadata_dict: Dict) -> dict:
- n = len(metadata_dict)
- with open(_TRAINING_CONFIG_PATH, "r") as file:
- training_config_dict = yaml.safe_load(file)
- with open(_LAY_DOWN_CONFIG_PATH, "r") as file:
- lay_down_config_dict = yaml.safe_load(file)
- averaged_data = {
- "start_timestamp": start_datetime.isoformat(),
- "end_datetime": datetime.now().isoformat(),
- "primaite_version": primaite.__version__,
- "system_info": _get_system_info(),
- "total_sessions": n,
- "total_episodes": sum(d["total_episodes"] for d in metadata_dict.values()),
- "total_time_steps": sum(d["total_time_steps"] for d in metadata_dict.values()),
- "av_s_per_session": sum(d["total_s"] for d in metadata_dict.values()) / n,
- "av_s_per_step": sum(d["s_per_step"] for d in metadata_dict.values()) / n,
- "av_s_per_100_steps_10_nodes": sum(d["s_per_100_steps_10_nodes"] for d in metadata_dict.values()) / n,
- "combined_av_reward_per_episode": {},
- "session_av_reward_per_episode": {k: v["av_reward_per_episode"] for k, v in metadata_dict.items()},
- "training_config": training_config_dict,
- "lay_down_config": lay_down_config_dict,
- }
-
- episodes = metadata_dict[1]["av_reward_per_episode"].keys()
-
- for episode in episodes:
- combined_av_reward = sum(metadata_dict[k]["av_reward_per_episode"][episode] for k in metadata_dict.keys()) / n
- averaged_data["combined_av_reward_per_episode"][episode] = combined_av_reward
-
- return averaged_data
-
-
-def _get_df_from_episode_av_reward_dict(data: Dict) -> pl.DataFrame:
- data: Dict = {"episode": data.keys(), "av_reward": data.values()}
-
- return (
- pl.from_dict(data)
- .with_columns(rolling_mean=pl.col("av_reward").rolling_mean(window_size=25))
- .rename({"rolling_mean": "rolling_av_reward"})
- )
-
-
-def _plot_benchmark_metadata(
- benchmark_metadata_dict: Dict,
- title: Optional[str] = None,
- subtitle: Optional[str] = None,
-) -> Figure:
- if title:
- if subtitle:
- title = f"{title}
{subtitle}"
- else:
- if subtitle:
- title = subtitle
-
- config = get_plotly_config()
- layout = go.Layout(
- autosize=config["size"]["auto_size"],
- width=config["size"]["width"],
- height=config["size"]["height"],
- )
- # Create the line graph with a colored line
- fig = go.Figure(layout=layout)
- fig.update_layout(template=config["template"])
-
- for session, av_reward_dict in benchmark_metadata_dict["session_av_reward_per_episode"].items():
- df = _get_df_from_episode_av_reward_dict(av_reward_dict)
- fig.add_trace(
- go.Scatter(
- x=df["episode"],
- y=df["av_reward"],
- mode="lines",
- name=f"Session {session}",
- opacity=0.25,
- line={"color": "#a6a6a6"},
- )
- )
-
- df = _get_df_from_episode_av_reward_dict(benchmark_metadata_dict["combined_av_reward_per_episode"])
- fig.add_trace(
- go.Scatter(
- x=df["episode"], y=df["av_reward"], mode="lines", name="Combined Session Av", line={"color": "#FF0000"}
- )
- )
-
- fig.add_trace(
- go.Scatter(
- x=df["episode"],
- y=df["rolling_av_reward"],
- mode="lines",
- name="Rolling Av (Combined Session Av)",
- line={"color": "#4CBB17"},
- )
- )
-
- # Set the layout of the graph
- fig.update_layout(
- xaxis={
- "title": "Episode",
- "type": "linear",
- },
- yaxis={"title": "Average Reward"},
- title=title,
- )
-
- return fig
-
-
-def _plot_all_benchmarks_combined_session_av() -> Figure:
+def _get_benchmark_primaite_environment() -> BenchmarkPrimaiteGymEnv:
"""
- Plot the Benchmark results for each released version of PrimAITE.
+ Create an instance of the BenchmarkPrimaiteGymEnv.
- Does this by iterating over the ``benchmark/results`` directory and
- extracting the benchmark metadata json for each version that has been
- benchmarked. The combined_av_reward_per_episode is extracted from each,
- converted into a polars dataframe, and plotted as a scatter line in plotly.
+ This environment will be used to train the agents on.
"""
- title = "PrimAITE Versions Learning Benchmark"
- subtitle = "Rolling Av (Combined Session Av)"
- if title:
- if subtitle:
- title = f"{title}
{subtitle}"
- else:
- if subtitle:
- title = subtitle
- config = get_plotly_config()
- layout = go.Layout(
- autosize=config["size"]["auto_size"],
- width=config["size"]["width"],
- height=config["size"]["height"],
- )
- # Create the line graph with a colored line
- fig = go.Figure(layout=layout)
- fig.update_layout(template=config["template"])
-
- for dir in _RESULTS_ROOT.iterdir():
- if dir.is_dir():
- metadata_file = dir / f"{dir.name}_benchmark_metadata.json"
- with open(metadata_file, "r") as file:
- metadata_dict = json.load(file)
- df = _get_df_from_episode_av_reward_dict(metadata_dict["combined_av_reward_per_episode"])
-
- fig.add_trace(go.Scatter(x=df["episode"], y=df["rolling_av_reward"], mode="lines", name=dir.name))
-
- # Set the layout of the graph
- fig.update_layout(
- xaxis={
- "title": "Episode",
- "type": "linear",
- },
- yaxis={"title": "Average Reward"},
- title=title,
- )
- fig["data"][0]["showlegend"] = True
-
- return fig
+ env = BenchmarkPrimaiteGymEnv(env_config=data_manipulation_config_path())
+ return env
-def run() -> None:
+def _prepare_session_directory():
+ """Prepare the session directory so that it is easier to clean up after the benchmarking is done."""
+ # override session path
+ session_path = _BENCHMARK_ROOT / "sessions"
+
+ if session_path.is_dir():
+ shutil.rmtree(session_path)
+
+ primaite.PRIMAITE_PATHS.user_sessions_path = session_path
+ primaite.PRIMAITE_PATHS.user_sessions_path.mkdir(exist_ok=True, parents=True)
+
+
+def run(
+ number_of_sessions: int = 5,
+ num_episodes: int = 1000,
+ episode_len: int = 128,
+ n_steps: int = 1280,
+ batch_size: int = 32,
+ learning_rate: float = 3e-4,
+) -> None:
"""Run the PrimAITE benchmark."""
- start_datetime = datetime.now()
- av_reward_per_episode_dicts = {}
- for i in range(1, 11):
+ benchmark_start_time = datetime.now()
+
+ session_metadata_dict = {}
+
+ _prepare_session_directory()
+
+ # run training
+ for i in range(1, number_of_sessions + 1):
print(f"Starting Benchmark Session: {i}")
- with _get_benchmark_primaite_session() as session:
- session.learn()
- av_reward_per_episode_dicts[i] = session.learn_metadata_dict()
- benchmark_metadata = _build_benchmark_results_dict(
- start_datetime=start_datetime, metadata_dict=av_reward_per_episode_dicts
+ with _get_benchmark_primaite_environment() as gym_env:
+ session = BenchmarkSession(
+ gym_env=gym_env,
+ num_episodes=num_episodes,
+ n_steps=n_steps,
+ episode_len=episode_len,
+ batch_size=batch_size,
+ learning_rate=learning_rate,
+ )
+ session.train()
+
+ # Dump the session metadata so that we're not holding it in memory as it's large
+ with open(_SESSION_METADATA_ROOT / f"{i}.json", "w") as file:
+ json.dump(session.session_metadata, file, indent=4)
+
+ for i in range(1, number_of_sessions + 1):
+ with open(_SESSION_METADATA_ROOT / f"{i}.json", "r") as file:
+ session_metadata_dict[i] = json.load(file)
+ # generate report
+ build_benchmark_latex_report(
+ benchmark_start_time=benchmark_start_time,
+ session_metadata=session_metadata_dict,
+ config_path=data_manipulation_config_path(),
+ results_root_path=_RESULTS_ROOT,
)
- v_str = f"v{primaite.__version__}"
-
- version_result_dir = _RESULTS_ROOT / v_str
- if version_result_dir.exists():
- shutil.rmtree(version_result_dir)
- version_result_dir.mkdir(exist_ok=True, parents=True)
-
- with open(version_result_dir / f"{v_str}_benchmark_metadata.json", "w") as file:
- json.dump(benchmark_metadata, file, indent=4)
- title = f"PrimAITE v{primaite.__version__.strip()} Learning Benchmark"
- fig = _plot_benchmark_metadata(benchmark_metadata, title=title)
- this_version_plot_path = version_result_dir / f"{title}.png"
- fig.write_image(this_version_plot_path)
-
- fig = _plot_all_benchmarks_combined_session_av()
-
- all_version_plot_path = _RESULTS_ROOT / "PrimAITE Versions Learning Benchmark.png"
- fig.write_image(all_version_plot_path)
-
- _build_benchmark_latex_report(benchmark_metadata, this_version_plot_path, all_version_plot_path)
if __name__ == "__main__":
diff --git a/benchmark/report.py b/benchmark/report.py
new file mode 100644
index 00000000..ca3d03a3
--- /dev/null
+++ b/benchmark/report.py
@@ -0,0 +1,305 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+import json
+import sys
+from datetime import datetime
+from pathlib import Path
+from typing import Dict, Optional
+
+import plotly.graph_objects as go
+import polars as pl
+import yaml
+from plotly.graph_objs import Figure
+from pylatex import Command, Document
+from pylatex import Figure as LatexFigure
+from pylatex import Section, Subsection, Tabular
+from pylatex.utils import bold
+from utils import _get_system_info
+
+import primaite
+
+PLOT_CONFIG = {
+ "size": {"auto_size": False, "width": 1500, "height": 900},
+ "template": "plotly_white",
+ "range_slider": False,
+}
+
+
+def _build_benchmark_results_dict(start_datetime: datetime, metadata_dict: Dict, config: Dict) -> dict:
+ num_sessions = len(metadata_dict) # number of sessions
+
+ averaged_data = {
+ "start_timestamp": start_datetime.isoformat(),
+ "end_datetime": datetime.now().isoformat(),
+ "primaite_version": primaite.__version__,
+ "system_info": _get_system_info(),
+ "total_sessions": num_sessions,
+ "total_episodes": sum(d["total_episodes"] for d in metadata_dict.values()),
+ "total_time_steps": sum(d["total_time_steps"] for d in metadata_dict.values()),
+ "av_s_per_session": sum(d["total_s"] for d in metadata_dict.values()) / num_sessions,
+ "av_s_per_step": sum(d["s_per_step"] for d in metadata_dict.values()) / num_sessions,
+ "av_s_per_100_steps_10_nodes": sum(d["s_per_100_steps_10_nodes"] for d in metadata_dict.values())
+ / num_sessions,
+ "combined_av_reward_per_episode": {},
+ "session_av_reward_per_episode": {k: v["av_reward_per_episode"] for k, v in metadata_dict.items()},
+ "config": config,
+ }
+
+ # find the average of each episode across all sessions
+ episodes = metadata_dict[1]["av_reward_per_episode"].keys()
+
+ for episode in episodes:
+ combined_av_reward = (
+ sum(metadata_dict[k]["av_reward_per_episode"][episode] for k in metadata_dict.keys()) / num_sessions
+ )
+ averaged_data["combined_av_reward_per_episode"][episode] = combined_av_reward
+
+ return averaged_data
+
+
+def _get_df_from_episode_av_reward_dict(data: Dict) -> pl.DataFrame:
+ data: Dict = {"episode": data.keys(), "av_reward": data.values()}
+
+ return (
+ pl.from_dict(data)
+ .with_columns(rolling_mean=pl.col("av_reward").rolling_mean(window_size=25))
+ .rename({"rolling_mean": "rolling_av_reward"})
+ )
+
+
+def _plot_benchmark_metadata(
+ benchmark_metadata_dict: Dict,
+ title: Optional[str] = None,
+ subtitle: Optional[str] = None,
+) -> Figure:
+ if title:
+ if subtitle:
+ title = f"{title}
{subtitle}"
+ else:
+ if subtitle:
+ title = subtitle
+
+ layout = go.Layout(
+ autosize=PLOT_CONFIG["size"]["auto_size"],
+ width=PLOT_CONFIG["size"]["width"],
+ height=PLOT_CONFIG["size"]["height"],
+ )
+ # Create the line graph with a colored line
+ fig = go.Figure(layout=layout)
+ fig.update_layout(template=PLOT_CONFIG["template"])
+
+ for session, av_reward_dict in benchmark_metadata_dict["session_av_reward_per_episode"].items():
+ df = _get_df_from_episode_av_reward_dict(av_reward_dict)
+ fig.add_trace(
+ go.Scatter(
+ x=df["episode"],
+ y=df["av_reward"],
+ mode="lines",
+ name=f"Session {session}",
+ opacity=0.25,
+ line={"color": "#a6a6a6"},
+ )
+ )
+
+ df = _get_df_from_episode_av_reward_dict(benchmark_metadata_dict["combined_av_reward_per_episode"])
+ fig.add_trace(
+ go.Scatter(
+ x=df["episode"], y=df["av_reward"], mode="lines", name="Combined Session Av", line={"color": "#FF0000"}
+ )
+ )
+
+ fig.add_trace(
+ go.Scatter(
+ x=df["episode"],
+ y=df["rolling_av_reward"],
+ mode="lines",
+ name="Rolling Av (Combined Session Av)",
+ line={"color": "#4CBB17"},
+ )
+ )
+
+ # Set the layout of the graph
+ fig.update_layout(
+ xaxis={
+ "title": "Episode",
+ "type": "linear",
+ },
+ yaxis={"title": "Total Reward"},
+ title=title,
+ )
+
+ return fig
+
+
+def _plot_all_benchmarks_combined_session_av(results_directory: Path) -> Figure:
+ """
+ Plot the Benchmark results for each released version of PrimAITE.
+
+ Does this by iterating over the ``benchmark/results`` directory and
+ extracting the benchmark metadata json for each version that has been
+ benchmarked. The combined_av_reward_per_episode is extracted from each,
+ converted into a polars dataframe, and plotted as a scatter line in plotly.
+ """
+ major_v = primaite.__version__.split(".")[0]
+ title = f"Learning Benchmarking of All Released Versions under Major v{major_v}.*.*"
+ subtitle = "Rolling Av (Combined Session Av)"
+ if title:
+ if subtitle:
+ title = f"{title}
{subtitle}"
+ else:
+ if subtitle:
+ title = subtitle
+ layout = go.Layout(
+ autosize=PLOT_CONFIG["size"]["auto_size"],
+ width=PLOT_CONFIG["size"]["width"],
+ height=PLOT_CONFIG["size"]["height"],
+ )
+ # Create the line graph with a colored line
+ fig = go.Figure(layout=layout)
+ fig.update_layout(template=PLOT_CONFIG["template"])
+
+ for dir in results_directory.iterdir():
+ if dir.is_dir():
+ metadata_file = dir / f"{dir.name}_benchmark_metadata.json"
+ with open(metadata_file, "r") as file:
+ metadata_dict = json.load(file)
+ df = _get_df_from_episode_av_reward_dict(metadata_dict["combined_av_reward_per_episode"])
+
+ fig.add_trace(go.Scatter(x=df["episode"], y=df["rolling_av_reward"], mode="lines", name=dir.name))
+
+ # Set the layout of the graph
+ fig.update_layout(
+ xaxis={
+ "title": "Episode",
+ "type": "linear",
+ },
+ yaxis={"title": "Total Reward"},
+ title=title,
+ )
+ fig["data"][0]["showlegend"] = True
+
+ return fig
+
+
+def build_benchmark_latex_report(
+ benchmark_start_time: datetime, session_metadata: Dict, config_path: Path, results_root_path: Path
+) -> None:
+ """Generates a latex report of the benchmark run."""
+ # generate report folder
+ v_str = f"v{primaite.__version__}"
+
+ version_result_dir = results_root_path / v_str
+ version_result_dir.mkdir(exist_ok=True, parents=True)
+
+ # load the config file as dict
+ with open(config_path, "r") as f:
+ cfg_data = yaml.safe_load(f)
+
+ # generate the benchmark metadata dict
+ benchmark_metadata_dict = _build_benchmark_results_dict(
+ start_datetime=benchmark_start_time, metadata_dict=session_metadata, config=cfg_data
+ )
+ major_v = primaite.__version__.split(".")[0]
+ with open(version_result_dir / f"{v_str}_benchmark_metadata.json", "w") as file:
+ json.dump(benchmark_metadata_dict, file, indent=4)
+ title = f"PrimAITE v{primaite.__version__.strip()} Learning Benchmark"
+ fig = _plot_benchmark_metadata(benchmark_metadata_dict, title=title)
+ this_version_plot_path = version_result_dir / f"{title}.png"
+ fig.write_image(this_version_plot_path)
+
+ fig = _plot_all_benchmarks_combined_session_av(results_directory=results_root_path)
+
+ all_version_plot_path = results_root_path / "PrimAITE Versions Learning Benchmark.png"
+ fig.write_image(all_version_plot_path)
+
+ geometry_options = {"tmargin": "2.5cm", "rmargin": "2.5cm", "bmargin": "2.5cm", "lmargin": "2.5cm"}
+ data = benchmark_metadata_dict
+ primaite_version = data["primaite_version"]
+
+ # Create a new document
+ doc = Document("report", geometry_options=geometry_options)
+ # Title
+ doc.preamble.append(Command("title", f"PrimAITE {primaite_version} Learning Benchmark"))
+ doc.preamble.append(Command("author", "PrimAITE Dev Team"))
+ doc.preamble.append(Command("date", datetime.now().date()))
+ doc.append(Command("maketitle"))
+
+ sessions = data["total_sessions"]
+ episodes = session_metadata[1]["total_episodes"] - 1
+ steps = data["config"]["game"]["max_episode_length"]
+
+ # Body
+ with doc.create(Section("Introduction")):
+ doc.append(
+ f"PrimAITE v{primaite_version} was benchmarked automatically upon release. Learning rate metrics "
+ f"were captured to be referenced during system-level testing and user acceptance testing (UAT)."
+ )
+ doc.append(
+ f"\nThe benchmarking process consists of running {sessions} training session using the same "
+ f"config file. Each session trains an agent for {episodes} episodes, "
+ f"with each episode consisting of {steps} steps."
+ )
+ doc.append(
+ f"\nThe total reward per episode from each session is captured. This is then used to calculate an "
+ f"caverage total reward per episode from the {sessions} individual sessions for smoothing. "
+ f"Finally, a 25-widow rolling average of the average total reward per session is calculated for "
+ f"further smoothing."
+ )
+
+ with doc.create(Section("System Information")):
+ with doc.create(Subsection("Python")):
+ with doc.create(Tabular("|l|l|")) as table:
+ table.add_hline()
+ table.add_row((bold("Version"), sys.version))
+ table.add_hline()
+ for section, section_data in data["system_info"].items():
+ if section_data:
+ with doc.create(Subsection(section)):
+ if isinstance(section_data, dict):
+ with doc.create(Tabular("|l|l|")) as table:
+ table.add_hline()
+ for key, value in section_data.items():
+ table.add_row((bold(key), value))
+ table.add_hline()
+ elif isinstance(section_data, list):
+ headers = section_data[0].keys()
+ tabs_str = "|".join(["l" for _ in range(len(headers))])
+ tabs_str = f"|{tabs_str}|"
+ with doc.create(Tabular(tabs_str)) as table:
+ table.add_hline()
+ table.add_row([bold(h) for h in headers])
+ table.add_hline()
+ for item in section_data:
+ table.add_row(item.values())
+ table.add_hline()
+
+ headers_map = {
+ "total_sessions": "Total Sessions",
+ "total_episodes": "Total Episodes",
+ "total_time_steps": "Total Steps",
+ "av_s_per_session": "Av Session Duration (s)",
+ "av_s_per_step": "Av Step Duration (s)",
+ "av_s_per_100_steps_10_nodes": "Av Duration per 100 Steps per 10 Nodes (s)",
+ }
+ with doc.create(Section("Stats")):
+ with doc.create(Subsection("Benchmark Results")):
+ with doc.create(Tabular("|l|l|")) as table:
+ table.add_hline()
+ for section, header in headers_map.items():
+ if section.startswith("av_"):
+ table.add_row((bold(header), f"{data[section]:.4f}"))
+ else:
+ table.add_row((bold(header), data[section]))
+ table.add_hline()
+
+ with doc.create(Section("Graphs")):
+ with doc.create(Subsection(f"v{primaite_version} Learning Benchmark Plot")):
+ with doc.create(LatexFigure(position="h!")) as pic:
+ pic.add_image(str(this_version_plot_path))
+ pic.add_caption(f"PrimAITE {primaite_version} Learning Benchmark Plot")
+
+ with doc.create(Subsection(f"Learning Benchmarking of All Released Versions under Major v{major_v}.*.*")):
+ with doc.create(LatexFigure(position="h!")) as pic:
+ pic.add_image(str(all_version_plot_path))
+ pic.add_caption(f"Learning Benchmarking of All Released Versions under Major v{major_v}.*.*")
+
+ doc.generate_pdf(str(this_version_plot_path).replace(".png", ""), clean_tex=True)
diff --git a/benchmark/results/PrimAITE Versions Learning Benchmark.png b/benchmark/results/v2/PrimAITE Versions Learning Benchmark.png
similarity index 100%
rename from benchmark/results/PrimAITE Versions Learning Benchmark.png
rename to benchmark/results/v2/PrimAITE Versions Learning Benchmark.png
diff --git a/benchmark/results/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.pdf b/benchmark/results/v2/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.pdf
similarity index 100%
rename from benchmark/results/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.pdf
rename to benchmark/results/v2/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.pdf
diff --git a/benchmark/results/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.png b/benchmark/results/v2/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.png
similarity index 100%
rename from benchmark/results/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.png
rename to benchmark/results/v2/v2.0.0/PrimAITE v2.0.0 Learning Benchmark.png
diff --git a/benchmark/results/v2.0.0/v2.0.0_benchmark_metadata.json b/benchmark/results/v2/v2.0.0/v2.0.0_benchmark_metadata.json
similarity index 100%
rename from benchmark/results/v2.0.0/v2.0.0_benchmark_metadata.json
rename to benchmark/results/v2/v2.0.0/v2.0.0_benchmark_metadata.json
diff --git a/benchmark/results/v3/PrimAITE Versions Learning Benchmark.png b/benchmark/results/v3/PrimAITE Versions Learning Benchmark.png
new file mode 100644
index 00000000..9884e2ec
Binary files /dev/null and b/benchmark/results/v3/PrimAITE Versions Learning Benchmark.png differ
diff --git a/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.pdf b/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.pdf
new file mode 100644
index 00000000..fceba624
Binary files /dev/null and b/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.pdf differ
diff --git a/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.png b/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.png
new file mode 100644
index 00000000..c54dc354
Binary files /dev/null and b/benchmark/results/v3/v3.0.0/PrimAITE v3.0.0 Learning Benchmark.png differ
diff --git a/benchmark/results/v3/v3.0.0/v3.0.0_benchmark_metadata.json b/benchmark/results/v3/v3.0.0/v3.0.0_benchmark_metadata.json
new file mode 100644
index 00000000..b6780eac
--- /dev/null
+++ b/benchmark/results/v3/v3.0.0/v3.0.0_benchmark_metadata.json
@@ -0,0 +1,7436 @@
+{
+ "start_timestamp": "2024-06-05T13:42:03.340305",
+ "end_datetime": "2024-06-05T18:09:12.905806",
+ "primaite_version": "3.0.0",
+ "system_info": {
+ "System": {
+ "OS": "Windows",
+ "OS Version": "10.0.22631",
+ "Machine": "AMD64",
+ "Processor": "Intel64 Family 6 Model 142 Stepping 12, GenuineIntel"
+ },
+ "CPU": {
+ "Physical Cores": 4,
+ "Total Cores": 8,
+ "Max Frequency": "2304.00Mhz"
+ },
+ "Memory": {
+ "Total": "15.68GB",
+ "Swap Total": "15.68GB"
+ },
+ "GPU": []
+ },
+ "total_sessions": 5,
+ "total_episodes": 5005,
+ "total_time_steps": 640000,
+ "av_s_per_session": 3205.6340542,
+ "av_s_per_step": 0.10017606419375,
+ "av_s_per_100_steps_10_nodes": 10.017606419375,
+ "combined_av_reward_per_episode": {
+ "1": -53.42999999999999,
+ "2": -25.18000000000001,
+ "3": -42.00000000000002,
+ "4": -49.20000000000003,
+ "5": -51.42999999999999,
+ "6": -32.34,
+ "7": -25.680000000000025,
+ "8": -23.03000000000001,
+ "9": -26.399999999999984,
+ "10": -49.52000000000006,
+ "11": -32.61,
+ "12": -27.349999999999994,
+ "13": -24.01000000000001,
+ "14": -19.69999999999998,
+ "15": -47.260000000000005,
+ "16": -47.400000000000034,
+ "17": -47.86000000000008,
+ "18": -27.699999999999996,
+ "19": -39.24000000000005,
+ "20": -32.030000000000015,
+ "21": -38.98000000000001,
+ "22": -14.029999999999982,
+ "23": -32.34999999999999,
+ "24": -28.94999999999997,
+ "25": -40.05999999999999,
+ "26": -32.91999999999998,
+ "27": -31.399999999999988,
+ "28": -37.15000000000001,
+ "29": -32.399999999999984,
+ "30": -47.72000000000002,
+ "31": -25.46,
+ "32": -30.720000000000017,
+ "33": -35.14,
+ "34": -13.459999999999976,
+ "35": -29.610000000000003,
+ "36": -22.61999999999999,
+ "37": -34.569999999999986,
+ "38": -6.239999999999982,
+ "39": -16.459999999999972,
+ "40": -30.310000000000002,
+ "41": -26.680000000000007,
+ "42": -45.540000000000006,
+ "43": -18.13999999999998,
+ "44": -15.73999999999998,
+ "45": -58.73999999999999,
+ "46": -24.20999999999997,
+ "47": -53.60999999999997,
+ "48": -23.170000000000005,
+ "49": -34.02,
+ "50": -24.039999999999992,
+ "51": -31.199999999999978,
+ "52": -24.649999999999967,
+ "53": -22.16999999999999,
+ "54": -30.20999999999998,
+ "55": -31.639999999999997,
+ "56": -35.949999999999974,
+ "57": -13.60999999999998,
+ "58": -25.879999999999978,
+ "59": -34.28999999999998,
+ "60": -50.72000000000003,
+ "61": -67.51,
+ "62": -26.78999999999998,
+ "63": -54.01000000000001,
+ "64": -15.929999999999987,
+ "65": -16.53,
+ "66": -36.31000000000002,
+ "67": -29.529999999999983,
+ "68": -22.96999999999997,
+ "69": -48.43999999999997,
+ "70": -34.84999999999995,
+ "71": -24.289999999999985,
+ "72": -17.87999999999998,
+ "73": -26.54999999999999,
+ "74": -8.489999999999975,
+ "75": -29.31000000000001,
+ "76": -14.089999999999995,
+ "77": -55.120000000000005,
+ "78": -24.11999999999999,
+ "79": -22.309999999999988,
+ "80": -36.84000000000002,
+ "81": -31.66999999999998,
+ "82": -37.70000000000003,
+ "83": -34.35,
+ "84": -26.069999999999983,
+ "85": -12.879999999999978,
+ "86": -40.050000000000054,
+ "87": -32.15999999999998,
+ "88": -31.560000000000024,
+ "89": -9.029999999999989,
+ "90": -33.70999999999998,
+ "91": -35.04999999999998,
+ "92": -31.67999999999998,
+ "93": -19.529999999999987,
+ "94": -41.85,
+ "95": -67.88,
+ "96": -69.57000000000002,
+ "97": -20.369999999999994,
+ "98": -26.72999999999999,
+ "99": -16.359999999999975,
+ "100": -7.610000000000008,
+ "101": -15.309999999999974,
+ "102": -26.65999999999999,
+ "103": -61.219999999999985,
+ "104": -36.86999999999997,
+ "105": -39.419999999999995,
+ "106": -6.009999999999972,
+ "107": -30.83000000000003,
+ "108": -46.370000000000005,
+ "109": -32.24,
+ "110": -38.27000000000001,
+ "111": -31.140000000000004,
+ "112": -19.309999999999995,
+ "113": -32.33999999999997,
+ "114": -20.619999999999983,
+ "115": -35.19000000000001,
+ "116": -32.71,
+ "117": -25.07000000000002,
+ "118": -29.50999999999994,
+ "119": -47.98999999999999,
+ "120": -22.109999999999992,
+ "121": -36.819999999999965,
+ "122": -24.610000000000007,
+ "123": -22.18999999999999,
+ "124": -31.9,
+ "125": -15.389999999999981,
+ "126": -35.01999999999999,
+ "127": -15.619999999999987,
+ "128": -23.289999999999985,
+ "129": -20.81000000000001,
+ "130": -13.230000000000018,
+ "131": -29.029999999999983,
+ "132": -3.4100000000000277,
+ "133": -13.549999999999988,
+ "134": -14.329999999999979,
+ "135": -32.87999999999999,
+ "136": -30.660000000000025,
+ "137": -30.820000000000004,
+ "138": -9.909999999999982,
+ "139": 3.299999999999995,
+ "140": -57.36999999999999,
+ "141": -25.150000000000002,
+ "142": -25.65999999999998,
+ "143": -15.309999999999992,
+ "144": -31.309999999999995,
+ "145": -14.769999999999971,
+ "146": -33.14999999999999,
+ "147": -22.149999999999984,
+ "148": -12.060000000000006,
+ "149": -7.089999999999975,
+ "150": -17.639999999999997,
+ "151": -51.56999999999998,
+ "152": -35.87000000000002,
+ "153": -40.41999999999999,
+ "154": -8.030000000000014,
+ "155": -2.66999999999999,
+ "156": -26.26,
+ "157": -14.999999999999991,
+ "158": -38.25999999999996,
+ "159": -45.96000000000001,
+ "160": -44.23999999999999,
+ "161": -17.700000000000006,
+ "162": -2.6099999999999914,
+ "163": -48.91999999999998,
+ "164": -45.160000000000004,
+ "165": -29.349999999999977,
+ "166": -31.33000000000002,
+ "167": -17.660000000000032,
+ "168": -46.59999999999998,
+ "169": -14.210000000000031,
+ "170": -45.92,
+ "171": -43.59000000000001,
+ "172": -37.029999999999994,
+ "173": -22.75999999999998,
+ "174": -27.220000000000006,
+ "175": -7.579999999999979,
+ "176": -15.040000000000001,
+ "177": -40.389999999999986,
+ "178": -16.31000000000001,
+ "179": -39.35,
+ "180": -1.8099999999999805,
+ "181": -14.559999999999965,
+ "182": -15.529999999999987,
+ "183": -28.41999999999998,
+ "184": -35.60999999999999,
+ "185": 5.010000000000003,
+ "186": -36.85000000000001,
+ "187": -32.35999999999997,
+ "188": -36.400000000000006,
+ "189": -26.220000000000006,
+ "190": -21.849999999999994,
+ "191": -12.069999999999979,
+ "192": -43.44999999999999,
+ "193": -29.52,
+ "194": -16.929999999999996,
+ "195": -17.710000000000008,
+ "196": 5.560000000000011,
+ "197": -23.619999999999976,
+ "198": -23.599999999999994,
+ "199": -32.259999999999984,
+ "200": -7.279999999999996,
+ "201": -21.439999999999987,
+ "202": -37.07000000000001,
+ "203": -30.399999999999977,
+ "204": -12.809999999999977,
+ "205": -51.85999999999997,
+ "206": 1.8800000000000083,
+ "207": -33.49,
+ "208": -3.0000000000000298,
+ "209": -23.970000000000006,
+ "210": -14.099999999999998,
+ "211": -17.349999999999977,
+ "212": -18.699999999999967,
+ "213": -17.15,
+ "214": -0.05000000000001137,
+ "215": -19.03999999999999,
+ "216": -11.849999999999989,
+ "217": -20.549999999999976,
+ "218": -10.709999999999992,
+ "219": -34.01999999999997,
+ "220": -21.840000000000003,
+ "221": -6.460000000000008,
+ "222": -20.040000000000013,
+ "223": -6.370000000000017,
+ "224": -32.69999999999999,
+ "225": -62.30999999999997,
+ "226": -4.909999999999997,
+ "227": -40.0,
+ "228": 19.44000000000001,
+ "229": -27.57000000000003,
+ "230": -0.3800000000000779,
+ "231": 11.459999999999937,
+ "232": -14.889999999999986,
+ "233": -6.519999999999982,
+ "234": -18.159999999999986,
+ "235": -41.589999999999996,
+ "236": -11.99000000000001,
+ "237": 10.859999999999928,
+ "238": 10.859999999999994,
+ "239": -22.35999999999999,
+ "240": -18.699999999999996,
+ "241": 12.349999999999959,
+ "242": -15.650000000000025,
+ "243": -19.18000000000002,
+ "244": 7.369999999999931,
+ "245": 8.569999999999974,
+ "246": -15.379999999999987,
+ "247": -6.399999999999995,
+ "248": 21.57999999999999,
+ "249": -0.1500000000000142,
+ "250": -19.23999999999999,
+ "251": -11.97000000000006,
+ "252": 1.739999999999999,
+ "253": 5.599999999999978,
+ "254": 14.960000000000047,
+ "255": 14.360000000000003,
+ "256": 11.219999999999926,
+ "257": 23.279999999999973,
+ "258": 14.319999999999975,
+ "259": -5.600000000000001,
+ "260": -10.239999999999998,
+ "261": 26.639999999999993,
+ "262": 14.999999999999968,
+ "263": -16.400000000000016,
+ "264": 28.329999999999956,
+ "265": 12.909999999999954,
+ "266": -33.24000000000002,
+ "267": 31.80999999999999,
+ "268": 9.439999999999944,
+ "269": 11.429999999999984,
+ "270": 31.479999999999922,
+ "271": 2.879999999999971,
+ "272": 13.180000000000003,
+ "273": 1.9899999999999896,
+ "274": 52.75000000000008,
+ "275": 23.439999999999976,
+ "276": 18.219999999999946,
+ "277": 26.07000000000004,
+ "278": 34.619999999999926,
+ "279": 22.119999999999983,
+ "280": 2.890000000000023,
+ "281": 0.48999999999998634,
+ "282": 11.729999999999972,
+ "283": -3.0600000000000307,
+ "284": -26.960000000000015,
+ "285": 16.130000000000052,
+ "286": 49.25999999999998,
+ "287": 19.15999999999999,
+ "288": 15.869999999999948,
+ "289": 34.890000000000065,
+ "290": 31.34999999999992,
+ "291": 39.719999999999985,
+ "292": 38.04000000000004,
+ "293": -10.500000000000039,
+ "294": 53.67,
+ "295": 42.92,
+ "296": 23.02999999999995,
+ "297": 41.94000000000001,
+ "298": 6.339999999999941,
+ "299": 57.34000000000001,
+ "300": 52.01000000000005,
+ "301": 65.96999999999993,
+ "302": 12.410000000000005,
+ "303": 21.090000000000025,
+ "304": 30.76999999999999,
+ "305": 33.58999999999996,
+ "306": 50.77999999999996,
+ "307": 51.159999999999954,
+ "308": 47.610000000000014,
+ "309": 55.55999999999999,
+ "310": 37.16999999999997,
+ "311": 51.229999999999976,
+ "312": 34.76999999999997,
+ "313": 46.38999999999999,
+ "314": 39.539999999999964,
+ "315": 37.12999999999997,
+ "316": 18.609999999999964,
+ "317": 31.299999999999937,
+ "318": 45.48,
+ "319": 66.16999999999999,
+ "320": 57.57999999999991,
+ "321": 47.49999999999997,
+ "322": 61.73000000000002,
+ "323": 18.960000000000036,
+ "324": 17.059999999999953,
+ "325": 29.950000000000017,
+ "326": 72.55999999999997,
+ "327": 56.239999999999974,
+ "328": 52.93000000000005,
+ "329": 66.8500000000001,
+ "330": 41.69000000000004,
+ "331": 44.66000000000001,
+ "332": 18.389999999999972,
+ "333": 59.40999999999999,
+ "334": 38.26,
+ "335": 35.409999999999954,
+ "336": 72.00000000000004,
+ "337": 49.43000000000005,
+ "338": 54.30000000000001,
+ "339": 34.12000000000001,
+ "340": 36.649999999999984,
+ "341": 61.93999999999998,
+ "342": 64.25,
+ "343": 77.73000000000002,
+ "344": 56.30999999999996,
+ "345": 33.15999999999998,
+ "346": 97.51000000000002,
+ "347": 66.30000000000004,
+ "348": 62.129999999999995,
+ "349": 66.78000000000004,
+ "350": 74.30000000000004,
+ "351": 84.32000000000005,
+ "352": 84.61000000000004,
+ "353": 84.73000000000005,
+ "354": 82.93000000000008,
+ "355": 68.25000000000004,
+ "356": 81.20999999999995,
+ "357": 86.05000000000004,
+ "358": 101.50000000000001,
+ "359": 52.18000000000001,
+ "360": 77.03999999999996,
+ "361": 56.35999999999997,
+ "362": 37.92000000000004,
+ "363": 55.38999999999997,
+ "364": 73.38999999999999,
+ "365": 87.18000000000009,
+ "366": 52.51999999999998,
+ "367": 79.56000000000002,
+ "368": 104.86000000000006,
+ "369": 90.67,
+ "370": 46.780000000000015,
+ "371": 95.87000000000006,
+ "372": 80.30000000000001,
+ "373": 89.15000000000005,
+ "374": 62.790000000000035,
+ "375": 79.02999999999996,
+ "376": 52.35999999999999,
+ "377": 71.50000000000004,
+ "378": 64.99000000000001,
+ "379": 85.16000000000001,
+ "380": 64.61999999999996,
+ "381": 86.38000000000007,
+ "382": 75.47999999999999,
+ "383": 90.79000000000008,
+ "384": 47.88000000000002,
+ "385": 78.22000000000006,
+ "386": 95.23000000000009,
+ "387": 62.260000000000005,
+ "388": 98.30000000000007,
+ "389": 96.87000000000009,
+ "390": 89.86000000000006,
+ "391": 81.84000000000006,
+ "392": 89.85999999999997,
+ "393": 85.64000000000006,
+ "394": 94.20000000000012,
+ "395": 84.8900000000001,
+ "396": 84.00000000000003,
+ "397": 87.8700000000001,
+ "398": 90.79000000000005,
+ "399": 89.76000000000003,
+ "400": 90.93000000000006,
+ "401": 92.94000000000003,
+ "402": 76.24000000000004,
+ "403": 100.1500000000001,
+ "404": 74.65000000000006,
+ "405": 93.45000000000009,
+ "406": 83.1,
+ "407": 88.18000000000009,
+ "408": 101.87000000000006,
+ "409": 98.75000000000007,
+ "410": 93.41000000000005,
+ "411": 87.29000000000005,
+ "412": 96.15000000000005,
+ "413": 61.90000000000001,
+ "414": 96.95000000000006,
+ "415": 88.66000000000004,
+ "416": 102.69000000000003,
+ "417": 92.90000000000006,
+ "418": 98.63000000000005,
+ "419": 87.88999999999993,
+ "420": 104.38000000000004,
+ "421": 105.45000000000012,
+ "422": 94.10999999999999,
+ "423": 103.0100000000001,
+ "424": 93.52000000000007,
+ "425": 95.01000000000008,
+ "426": 92.73000000000003,
+ "427": 61.749999999999986,
+ "428": 106.69000000000005,
+ "429": 67.73999999999998,
+ "430": 101.39000000000009,
+ "431": 93.69000000000008,
+ "432": 103.09000000000007,
+ "433": 100.92000000000014,
+ "434": 108.52000000000005,
+ "435": 102.89000000000007,
+ "436": 97.18000000000002,
+ "437": 83.02999999999997,
+ "438": 102.86000000000004,
+ "439": 79.78,
+ "440": 84.15000000000012,
+ "441": 100.4400000000001,
+ "442": 103.63000000000008,
+ "443": 99.71000000000006,
+ "444": 106.74000000000008,
+ "445": 110.36000000000008,
+ "446": 99.64000000000001,
+ "447": 101.71000000000006,
+ "448": 106.89000000000013,
+ "449": 100.66000000000007,
+ "450": 84.13000000000007,
+ "451": 88.16000000000001,
+ "452": 98.79000000000005,
+ "453": 96.48000000000008,
+ "454": 98.1000000000001,
+ "455": 100.49000000000011,
+ "456": 102.00000000000009,
+ "457": 80.97000000000006,
+ "458": 106.74000000000005,
+ "459": 92.44000000000001,
+ "460": 83.60000000000005,
+ "461": 96.57000000000002,
+ "462": 96.95,
+ "463": 105.86000000000008,
+ "464": 86.39000000000006,
+ "465": 100.77000000000007,
+ "466": 98.18,
+ "467": 108.3600000000001,
+ "468": 103.9400000000001,
+ "469": 99.2800000000001,
+ "470": 112.54000000000008,
+ "471": 107.64000000000003,
+ "472": 99.98999999999998,
+ "473": 91.29999999999998,
+ "474": 99.01000000000003,
+ "475": 98.15,
+ "476": 82.97000000000007,
+ "477": 103.87000000000003,
+ "478": 89.69000000000004,
+ "479": 99.9100000000001,
+ "480": 101.79000000000006,
+ "481": 109.69000000000003,
+ "482": 110.00000000000009,
+ "483": 109.32000000000012,
+ "484": 93.60000000000007,
+ "485": 95.79999999999994,
+ "486": 97.5800000000001,
+ "487": 104.71000000000008,
+ "488": 91.97000000000007,
+ "489": 103.03000000000004,
+ "490": 98.50000000000001,
+ "491": 107.53,
+ "492": 107.98999999999998,
+ "493": 102.28999999999999,
+ "494": 106.03000000000009,
+ "495": 85.19000000000005,
+ "496": 98.03,
+ "497": 107.60999999999999,
+ "498": 113.52000000000014,
+ "499": 67.78,
+ "500": 107.51000000000008,
+ "501": 111.15000000000005,
+ "502": 107.94000000000008,
+ "503": 111.25000000000007,
+ "504": 92.46000000000006,
+ "505": 110.73000000000006,
+ "506": 106.95000000000007,
+ "507": 112.69000000000008,
+ "508": 100.75000000000004,
+ "509": 102.05000000000005,
+ "510": 111.5500000000001,
+ "511": 84.91000000000012,
+ "512": 99.25000000000006,
+ "513": 75.33000000000001,
+ "514": 108.66000000000008,
+ "515": 99.84,
+ "516": 106.1000000000001,
+ "517": 90.51000000000008,
+ "518": 94.95000000000006,
+ "519": 113.2700000000001,
+ "520": 104.46000000000006,
+ "521": 109.72000000000017,
+ "522": 95.16999999999999,
+ "523": 111.41000000000004,
+ "524": 112.0900000000001,
+ "525": 102.2800000000001,
+ "526": 74.75000000000003,
+ "527": 112.96000000000004,
+ "528": 112.05,
+ "529": 88.92000000000004,
+ "530": 92.04000000000005,
+ "531": 111.37000000000009,
+ "532": 96.09000000000012,
+ "533": 87.22000000000003,
+ "534": 105.06000000000002,
+ "535": 100.41999999999999,
+ "536": 104.68000000000004,
+ "537": 98.40000000000006,
+ "538": 76.62000000000005,
+ "539": 104.88000000000004,
+ "540": 91.47000000000001,
+ "541": 109.36000000000008,
+ "542": 106.11000000000006,
+ "543": 100.07000000000005,
+ "544": 108.85000000000005,
+ "545": 114.59000000000015,
+ "546": 109.05000000000004,
+ "547": 97.19,
+ "548": 108.62000000000009,
+ "549": 99.49000000000001,
+ "550": 108.29000000000008,
+ "551": 76.75000000000007,
+ "552": 77.51,
+ "553": 100.11000000000004,
+ "554": 98.33000000000007,
+ "555": 96.36000000000003,
+ "556": 71.85000000000008,
+ "557": 100.52000000000005,
+ "558": 106.76000000000008,
+ "559": 77.76000000000005,
+ "560": 86.00000000000003,
+ "561": 103.4400000000001,
+ "562": 102.25000000000001,
+ "563": 95.7300000000001,
+ "564": 105.78000000000004,
+ "565": 81.67000000000003,
+ "566": 69.25000000000001,
+ "567": 84.89000000000013,
+ "568": 85.57000000000002,
+ "569": 84.37999999999998,
+ "570": 110.12000000000005,
+ "571": 76.47000000000003,
+ "572": 107.25000000000004,
+ "573": 103.12000000000008,
+ "574": 92.90000000000002,
+ "575": 71.58000000000001,
+ "576": 101.26000000000005,
+ "577": 108.97000000000007,
+ "578": 99.80000000000007,
+ "579": 98.73000000000012,
+ "580": 102.08000000000004,
+ "581": 102.90000000000006,
+ "582": 93.73000000000005,
+ "583": 96.41,
+ "584": 108.49000000000017,
+ "585": 102.98999999999998,
+ "586": 108.66000000000015,
+ "587": 103.58999999999999,
+ "588": 107.59999999999998,
+ "589": 108.62000000000012,
+ "590": 110.27000000000012,
+ "591": 89.32000000000002,
+ "592": 94.60000000000007,
+ "593": 92.90000000000005,
+ "594": 92.42000000000004,
+ "595": 105.41000000000004,
+ "596": 99.83,
+ "597": 101.31000000000003,
+ "598": 97.30000000000004,
+ "599": 88.44000000000008,
+ "600": 98.44000000000007,
+ "601": 106.03000000000013,
+ "602": 106.07000000000012,
+ "603": 85.46000000000014,
+ "604": 109.87000000000008,
+ "605": 106.56000000000009,
+ "606": 96.01000000000002,
+ "607": 85.52000000000001,
+ "608": 74.34000000000003,
+ "609": 107.61000000000008,
+ "610": 100.89000000000007,
+ "611": 92.68000000000009,
+ "612": 101.27999999999994,
+ "613": 95.65000000000005,
+ "614": 111.29000000000005,
+ "615": 111.60000000000014,
+ "616": 100.77000000000005,
+ "617": 84.43000000000009,
+ "618": 93.43000000000002,
+ "619": 111.56000000000009,
+ "620": 111.04000000000008,
+ "621": 93.54,
+ "622": 105.55000000000007,
+ "623": 106.9200000000001,
+ "624": 90.46000000000006,
+ "625": 94.35000000000008,
+ "626": 107.47000000000006,
+ "627": 107.85,
+ "628": 99.71999999999998,
+ "629": 106.29000000000012,
+ "630": 103.92000000000007,
+ "631": 111.71000000000008,
+ "632": 104.81000000000003,
+ "633": 93.88000000000002,
+ "634": 102.31000000000002,
+ "635": 97.68000000000004,
+ "636": 111.31999999999998,
+ "637": 111.26000000000006,
+ "638": 111.15000000000009,
+ "639": 108.30000000000011,
+ "640": 80.35000000000008,
+ "641": 75.79999999999995,
+ "642": 94.97000000000003,
+ "643": 97.2300000000001,
+ "644": 82.43999999999998,
+ "645": 106.8100000000001,
+ "646": 90.74000000000004,
+ "647": 97.49000000000007,
+ "648": 94.85000000000007,
+ "649": 102.53000000000009,
+ "650": 100.30000000000004,
+ "651": 99.18000000000008,
+ "652": 100.18000000000009,
+ "653": 97.75000000000004,
+ "654": 105.00000000000003,
+ "655": 94.92000000000002,
+ "656": 103.94000000000001,
+ "657": 97.40000000000006,
+ "658": 95.91999999999999,
+ "659": 98.24000000000002,
+ "660": 101.10000000000001,
+ "661": 101.91,
+ "662": 105.82000000000009,
+ "663": 88.70000000000007,
+ "664": 95.59,
+ "665": 105.51000000000003,
+ "666": 103.15000000000009,
+ "667": 77.63000000000002,
+ "668": 109.07000000000008,
+ "669": 104.1000000000001,
+ "670": 107.96000000000011,
+ "671": 103.84000000000006,
+ "672": 105.64000000000003,
+ "673": 89.55999999999999,
+ "674": 104.09000000000006,
+ "675": 110.71000000000008,
+ "676": 82.57000000000005,
+ "677": 109.10000000000007,
+ "678": 110.3400000000001,
+ "679": 108.3600000000001,
+ "680": 112.8000000000001,
+ "681": 96.88000000000005,
+ "682": 102.44000000000003,
+ "683": 98.23999999999998,
+ "684": 96.39000000000006,
+ "685": 111.07000000000012,
+ "686": 83.20999999999994,
+ "687": 107.7,
+ "688": 102.35000000000007,
+ "689": 111.86000000000004,
+ "690": 109.91000000000011,
+ "691": 83.71000000000001,
+ "692": 100.70000000000012,
+ "693": 109.61000000000004,
+ "694": 103.18999999999997,
+ "695": 91.47000000000011,
+ "696": 107.89000000000003,
+ "697": 101.27000000000007,
+ "698": 94.35000000000005,
+ "699": 111.98000000000013,
+ "700": 106.38000000000002,
+ "701": 94.61000000000008,
+ "702": 101.75000000000003,
+ "703": 106.99000000000005,
+ "704": 110.75000000000009,
+ "705": 109.20000000000012,
+ "706": 110.56000000000009,
+ "707": 111.45000000000005,
+ "708": 111.41000000000012,
+ "709": 107.53000000000002,
+ "710": 105.27000000000014,
+ "711": 106.04000000000005,
+ "712": 109.6400000000001,
+ "713": 104.43000000000009,
+ "714": 107.25000000000007,
+ "715": 103.58000000000004,
+ "716": 108.03000000000009,
+ "717": 108.82000000000005,
+ "718": 91.42,
+ "719": 108.64000000000001,
+ "720": 112.89000000000006,
+ "721": 111.45000000000007,
+ "722": 111.46000000000006,
+ "723": 97.90000000000002,
+ "724": 110.77000000000007,
+ "725": 111.97000000000003,
+ "726": 110.19000000000005,
+ "727": 98.41000000000003,
+ "728": 108.12000000000003,
+ "729": 113.6000000000001,
+ "730": 107.56000000000006,
+ "731": 111.6100000000001,
+ "732": 108.04000000000003,
+ "733": 110.37999999999997,
+ "734": 114.6800000000001,
+ "735": 88.51000000000005,
+ "736": 95.48999999999997,
+ "737": 93.22000000000004,
+ "738": 103.04000000000005,
+ "739": 109.81000000000006,
+ "740": 106.0900000000001,
+ "741": 102.90000000000005,
+ "742": 83.93000000000004,
+ "743": 93.63000000000011,
+ "744": 108.48000000000006,
+ "745": 101.40000000000005,
+ "746": 107.83000000000007,
+ "747": 111.72000000000007,
+ "748": 103.55000000000004,
+ "749": 105.63000000000004,
+ "750": 103.91000000000008,
+ "751": 102.59000000000012,
+ "752": 108.02000000000002,
+ "753": 82.87000000000005,
+ "754": 101.22000000000006,
+ "755": 98.05000000000007,
+ "756": 108.96000000000004,
+ "757": 109.53000000000004,
+ "758": 101.35000000000005,
+ "759": 113.43000000000009,
+ "760": 111.06000000000013,
+ "761": 111.89000000000014,
+ "762": 109.84000000000006,
+ "763": 106.32000000000008,
+ "764": 97.49999999999999,
+ "765": 108.57000000000005,
+ "766": 111.83000000000007,
+ "767": 108.75000000000004,
+ "768": 110.96000000000004,
+ "769": 113.68000000000013,
+ "770": 108.16000000000008,
+ "771": 112.68000000000002,
+ "772": 105.77000000000002,
+ "773": 107.92000000000004,
+ "774": 110.19000000000003,
+ "775": 107.47000000000007,
+ "776": 110.5900000000001,
+ "777": 114.56000000000003,
+ "778": 112.06000000000013,
+ "779": 112.07000000000014,
+ "780": 111.21000000000008,
+ "781": 96.50000000000003,
+ "782": 102.43000000000002,
+ "783": 109.47000000000007,
+ "784": 111.58000000000001,
+ "785": 111.42000000000003,
+ "786": 107.28000000000009,
+ "787": 114.24000000000015,
+ "788": 109.28000000000009,
+ "789": 107.0600000000001,
+ "790": 113.78000000000016,
+ "791": 105.17000000000012,
+ "792": 97.64999999999995,
+ "793": 109.31000000000003,
+ "794": 110.45000000000009,
+ "795": 112.08000000000001,
+ "796": 112.21000000000004,
+ "797": 108.54000000000005,
+ "798": 109.67000000000012,
+ "799": 84.29000000000003,
+ "800": 110.61000000000008,
+ "801": 112.08000000000007,
+ "802": 113.32000000000008,
+ "803": 112.37000000000005,
+ "804": 108.25000000000004,
+ "805": 111.52000000000014,
+ "806": 109.90000000000006,
+ "807": 112.44000000000008,
+ "808": 107.66000000000008,
+ "809": 108.48000000000009,
+ "810": 111.1800000000001,
+ "811": 111.90000000000006,
+ "812": 114.39000000000003,
+ "813": 110.7400000000001,
+ "814": 98.65,
+ "815": 110.52000000000012,
+ "816": 107.75000000000007,
+ "817": 109.60000000000005,
+ "818": 112.74000000000008,
+ "819": 114.99000000000012,
+ "820": 105.29999999999995,
+ "821": 109.6000000000001,
+ "822": 112.20000000000005,
+ "823": 111.93000000000002,
+ "824": 107.15000000000005,
+ "825": 111.55000000000007,
+ "826": 111.77999999999997,
+ "827": 104.37999999999997,
+ "828": 107.96000000000004,
+ "829": 109.4,
+ "830": 112.2,
+ "831": 93.95,
+ "832": 114.2900000000001,
+ "833": 111.49000000000008,
+ "834": 110.1900000000001,
+ "835": 108.77000000000005,
+ "836": 75.7,
+ "837": 95.42000000000004,
+ "838": 97.78000000000003,
+ "839": 110.7200000000001,
+ "840": 107.88000000000011,
+ "841": 101.18000000000004,
+ "842": 113.23000000000006,
+ "843": 110.07000000000002,
+ "844": 108.8000000000001,
+ "845": 112.33000000000008,
+ "846": 105.64000000000007,
+ "847": 100.72000000000007,
+ "848": 105.25000000000003,
+ "849": 109.75000000000007,
+ "850": 91.50000000000003,
+ "851": 74.92,
+ "852": 104.97000000000006,
+ "853": 105.62000000000003,
+ "854": 71.96,
+ "855": 109.47,
+ "856": 107.39000000000003,
+ "857": 114.7900000000001,
+ "858": 109.87000000000003,
+ "859": 96.55999999999997,
+ "860": 112.41000000000012,
+ "861": 85.77000000000005,
+ "862": 113.43000000000013,
+ "863": 110.76000000000013,
+ "864": 86.15000000000005,
+ "865": 108.96000000000011,
+ "866": 74.38999999999997,
+ "867": 111.89000000000014,
+ "868": 105.6400000000001,
+ "869": 85.18999999999998,
+ "870": 90.26000000000005,
+ "871": 88.56000000000007,
+ "872": 79.2900000000001,
+ "873": 109.81000000000006,
+ "874": 106.53000000000004,
+ "875": 90.01000000000002,
+ "876": 109.41000000000004,
+ "877": 105.73000000000002,
+ "878": 113.07000000000008,
+ "879": 110.85000000000007,
+ "880": 111.05000000000004,
+ "881": 96.38999999999996,
+ "882": 107.95000000000009,
+ "883": 92.63000000000007,
+ "884": 82.78999999999994,
+ "885": 108.46000000000006,
+ "886": 99.02000000000004,
+ "887": 109.95000000000007,
+ "888": 104.32000000000002,
+ "889": 114.87000000000008,
+ "890": 103.4300000000001,
+ "891": 110.88000000000011,
+ "892": 93.03000000000006,
+ "893": 105.53000000000004,
+ "894": 99.39000000000001,
+ "895": 106.48000000000009,
+ "896": 108.43000000000009,
+ "897": 96.05000000000005,
+ "898": 109.5900000000001,
+ "899": 104.57000000000005,
+ "900": 107.19000000000005,
+ "901": 111.14000000000007,
+ "902": 99.48000000000009,
+ "903": 76.02000000000005,
+ "904": 111.19000000000003,
+ "905": 93.26000000000003,
+ "906": 106.86000000000013,
+ "907": 113.82000000000009,
+ "908": 111.56000000000009,
+ "909": 107.29000000000008,
+ "910": 97.72999999999999,
+ "911": 107.19000000000008,
+ "912": 109.11000000000013,
+ "913": 114.19000000000008,
+ "914": 86.27000000000007,
+ "915": 110.64000000000007,
+ "916": 111.99000000000005,
+ "917": 102.81000000000006,
+ "918": 110.01000000000013,
+ "919": 87.88000000000008,
+ "920": 107.4300000000001,
+ "921": 112.51000000000008,
+ "922": 112.38000000000011,
+ "923": 111.66000000000004,
+ "924": 109.02000000000002,
+ "925": 102.73000000000006,
+ "926": 112.62000000000009,
+ "927": 110.83000000000008,
+ "928": 114.02000000000005,
+ "929": 96.5,
+ "930": 112.74000000000008,
+ "931": 106.63000000000007,
+ "932": 107.65000000000006,
+ "933": 109.5500000000001,
+ "934": 102.63999999999996,
+ "935": 113.26000000000008,
+ "936": 111.31000000000006,
+ "937": 111.13000000000007,
+ "938": 112.09000000000007,
+ "939": 111.2400000000001,
+ "940": 101.72999999999999,
+ "941": 93.6500000000001,
+ "942": 111.97000000000003,
+ "943": 114.54000000000008,
+ "944": 107.3600000000001,
+ "945": 113.56000000000009,
+ "946": 112.25000000000007,
+ "947": 110.04000000000005,
+ "948": 102.43000000000004,
+ "949": 109.00000000000004,
+ "950": 111.21000000000006,
+ "951": 104.88000000000007,
+ "952": 109.62999999999997,
+ "953": 113.76000000000003,
+ "954": 115.7700000000001,
+ "955": 104.64000000000006,
+ "956": 110.82000000000002,
+ "957": 104.98000000000006,
+ "958": 108.25000000000009,
+ "959": 114.3900000000001,
+ "960": 108.59000000000007,
+ "961": 112.2400000000001,
+ "962": 93.43999999999993,
+ "963": 109.91000000000005,
+ "964": 106.43000000000002,
+ "965": 107.78000000000006,
+ "966": 112.47000000000011,
+ "967": 107.00000000000011,
+ "968": 111.91000000000008,
+ "969": 108.42000000000003,
+ "970": 116.22000000000003,
+ "971": 104.26000000000003,
+ "972": 112.59000000000012,
+ "973": 112.0500000000001,
+ "974": 111.07000000000005,
+ "975": 114.0900000000001,
+ "976": 110.94000000000001,
+ "977": 108.6800000000001,
+ "978": 106.68000000000006,
+ "979": 102.15000000000008,
+ "980": 110.1700000000001,
+ "981": 71.59000000000002,
+ "982": 111.89000000000003,
+ "983": 110.4200000000001,
+ "984": 92.44000000000008,
+ "985": 113.17999999999999,
+ "986": 106.27000000000007,
+ "987": 112.91000000000005,
+ "988": 111.20000000000005,
+ "989": 106.60000000000005,
+ "990": 115.01000000000006,
+ "991": 109.79000000000003,
+ "992": 110.43000000000006,
+ "993": 109.84000000000003,
+ "994": 91.15000000000005,
+ "995": 111.5400000000001,
+ "996": 74.26000000000008,
+ "997": 111.37000000000003,
+ "998": 108.57000000000002,
+ "999": 112.19000000000008,
+ "1000": 110.26000000000008
+ },
+ "session_av_reward_per_episode": {
+ "1": {
+ "1": -71.44999999999999,
+ "2": -50.300000000000075,
+ "3": -42.350000000000044,
+ "4": -45.55000000000017,
+ "5": -78.14999999999999,
+ "6": -72.10000000000004,
+ "7": -21.749999999999957,
+ "8": -16.299999999999972,
+ "9": -33.999999999999986,
+ "10": -38.40000000000004,
+ "11": -8.549999999999997,
+ "12": -10.250000000000009,
+ "13": -19.09999999999997,
+ "14": -21.599999999999955,
+ "15": -53.50000000000008,
+ "16": -59.450000000000095,
+ "17": -71.10000000000004,
+ "18": -66.20000000000007,
+ "19": -39.9000000000001,
+ "20": -21.199999999999957,
+ "21": -19.849999999999966,
+ "22": -4.299999999999994,
+ "23": -20.799999999999958,
+ "24": -19.899999999999963,
+ "25": -99.69999999999999,
+ "26": -22.699999999999953,
+ "27": -80.20000000000005,
+ "28": -40.450000000000074,
+ "29": -95.05000000000001,
+ "30": -72.85,
+ "31": -37.500000000000036,
+ "32": -6.9499999999999975,
+ "33": -22.499999999999954,
+ "34": -20.450000000000006,
+ "35": -66.45000000000007,
+ "36": -36.50000000000001,
+ "37": -30.500000000000018,
+ "38": -2.7999999999999856,
+ "39": -20.999999999999957,
+ "40": -7.8999999999999995,
+ "41": -13.749999999999988,
+ "42": -9.249999999999996,
+ "43": -38.64999999999994,
+ "44": -16.899999999999974,
+ "45": -72.70000000000002,
+ "46": -21.14999999999996,
+ "47": -12.549999999999988,
+ "48": -18.04999999999996,
+ "49": -34.35000000000006,
+ "50": -23.24999999999995,
+ "51": -93.29999999999998,
+ "52": -17.74999999999997,
+ "53": -19.099999999999966,
+ "54": -15.199999999999978,
+ "55": -68.90000000000003,
+ "56": -10.099999999999984,
+ "57": -17.299999999999972,
+ "58": -18.449999999999967,
+ "59": -75.09999999999997,
+ "60": -97.44999999999999,
+ "61": -18.749999999999968,
+ "62": -10.299999999999988,
+ "63": -67.85,
+ "64": -1.8999999999999924,
+ "65": 3.750000000000023,
+ "66": -11.699999999999992,
+ "67": -4.599999999999996,
+ "68": -16.09999999999998,
+ "69": -23.19999999999995,
+ "70": -17.54999999999997,
+ "71": -15.849999999999973,
+ "72": -18.44999999999997,
+ "73": -18.149999999999967,
+ "74": -20.099999999999962,
+ "75": -6.349999999999987,
+ "76": -2.149999999999986,
+ "77": -17.699999999999978,
+ "78": -15.949999999999978,
+ "79": -18.09999999999997,
+ "80": -11.349999999999996,
+ "81": -88.25,
+ "82": -4.249999999999991,
+ "83": -10.699999999999994,
+ "84": -22.899999999999952,
+ "85": -9.399999999999988,
+ "86": -6.650000000000006,
+ "87": -21.749999999999954,
+ "88": -18.84999999999997,
+ "89": 1.550000000000002,
+ "90": -15.699999999999974,
+ "91": -94.24999999999994,
+ "92": -17.249999999999968,
+ "93": -20.899999999999974,
+ "94": 12.799999999999997,
+ "95": -77.55,
+ "96": -88.6,
+ "97": -11.00000000000001,
+ "98": 2.500000000000029,
+ "99": -28.199999999999996,
+ "100": -29.100000000000023,
+ "101": -10.049999999999986,
+ "102": -22.99999999999993,
+ "103": -84.25,
+ "104": -8.749999999999993,
+ "105": -13.849999999999987,
+ "106": -3.2499999999999787,
+ "107": -34.050000000000054,
+ "108": -19.449999999999964,
+ "109": -11.649999999999988,
+ "110": -37.100000000000065,
+ "111": -35.150000000000055,
+ "112": -32.599999999999994,
+ "113": -73.55,
+ "114": -19.649999999999963,
+ "115": -82.49999999999997,
+ "116": -15.44999999999998,
+ "117": -41.650000000000134,
+ "118": -19.449999999999932,
+ "119": -72.04999999999998,
+ "120": -76.2,
+ "121": -20.799999999999958,
+ "122": -21.199999999999978,
+ "123": 7.9999999999999885,
+ "124": -19.949999999999964,
+ "125": -15.999999999999977,
+ "126": -64.84999999999997,
+ "127": -18.899999999999967,
+ "128": -26.34999999999998,
+ "129": -16.449999999999978,
+ "130": 23.149999999999917,
+ "131": 4.550000000000013,
+ "132": -16.699999999999978,
+ "133": 10.450000000000077,
+ "134": 16.000000000000018,
+ "135": -77.05000000000001,
+ "136": -55.8000000000001,
+ "137": -0.24999999999996714,
+ "138": -12.199999999999989,
+ "139": -0.7000000000000068,
+ "140": -72.64999999999995,
+ "141": 23.34999999999991,
+ "142": -21.149999999999956,
+ "143": -3.199999999999977,
+ "144": -54.9,
+ "145": -10.799999999999988,
+ "146": -8.15,
+ "147": -66.95000000000002,
+ "148": 11.300000000000015,
+ "149": -4.6499999999999995,
+ "150": -70.55000000000005,
+ "151": -66.99999999999994,
+ "152": -2.45,
+ "153": -79.5,
+ "154": -2.3000000000000034,
+ "155": 2.6000000000000396,
+ "156": 3.850000000000015,
+ "157": 7.150000000000051,
+ "158": -76.2999999999999,
+ "159": -67.90000000000002,
+ "160": -13.649999999999979,
+ "161": -6.34999999999999,
+ "162": -9.39999999999999,
+ "163": -56.75,
+ "164": -78.69999999999992,
+ "165": -4.649999999999984,
+ "166": 29.949999999999864,
+ "167": -79.69999999999999,
+ "168": -92.5,
+ "169": 0.6499999999999813,
+ "170": -80.45000000000003,
+ "171": -59.349999999999994,
+ "172": -61.250000000000014,
+ "173": -82.94999999999999,
+ "174": -70.99999999999997,
+ "175": -3.4999999999999902,
+ "176": -1.1499999999999755,
+ "177": -68.94999999999996,
+ "178": -35.49999999999999,
+ "179": -23.64999999999995,
+ "180": -11.049999999999983,
+ "181": 10.800000000000066,
+ "182": -14.100000000000007,
+ "183": -57.400000000000006,
+ "184": -47.45,
+ "185": -16.149999999999974,
+ "186": -22.65000000000006,
+ "187": -72.99999999999999,
+ "188": -79.5,
+ "189": -70.94999999999999,
+ "190": 3.6499999999999857,
+ "191": -19.79999999999995,
+ "192": -74.80000000000001,
+ "193": -33.85000000000004,
+ "194": -1.3999999999999708,
+ "195": -3.049999999999988,
+ "196": 23.14999999999998,
+ "197": -57.250000000000014,
+ "198": -13.799999999999985,
+ "199": -25.89999999999998,
+ "200": -1.8999999999999737,
+ "201": -26.45,
+ "202": -95.95,
+ "203": -80.69999999999996,
+ "204": 22.550000000000015,
+ "205": -84.64999999999988,
+ "206": 13.950000000000035,
+ "207": -75.45000000000006,
+ "208": -73.29999999999993,
+ "209": -20.200000000000028,
+ "210": 4.650000000000018,
+ "211": -64.94999999999996,
+ "212": 1.0499999999999912,
+ "213": -14.399999999999993,
+ "214": -76.95000000000005,
+ "215": 33.400000000000006,
+ "216": 33.30000000000001,
+ "217": -80.0,
+ "218": 5.349999999999952,
+ "219": 70.70000000000009,
+ "220": -22.150000000000016,
+ "221": -72.50000000000003,
+ "222": -44.49999999999999,
+ "223": -58.100000000000016,
+ "224": -3.4999999999999813,
+ "225": -55.64999999999999,
+ "226": -29.94999999999998,
+ "227": -88.39999999999998,
+ "228": 15.400000000000023,
+ "229": -70.74999999999997,
+ "230": -10.250000000000012,
+ "231": -5.80000000000002,
+ "232": -1.5999999999999903,
+ "233": 14.600000000000033,
+ "234": -7.499999999999994,
+ "235": -43.59999999999995,
+ "236": -2.850000000000028,
+ "237": -12.200000000000015,
+ "238": 14.750000000000032,
+ "239": -6.249999999999982,
+ "240": -55.099999999999945,
+ "241": 3.3306690738754696e-15,
+ "242": 54.8499999999999,
+ "243": 1.7000000000000317,
+ "244": -1.8000000000000227,
+ "245": 1.9000000000000663,
+ "246": 0.5999999999999699,
+ "247": -73.65000000000003,
+ "248": 11.399999999999952,
+ "249": -55.95,
+ "250": -69.69999999999999,
+ "251": -68.15,
+ "252": -29.750000000000025,
+ "253": -45.65000000000002,
+ "254": 2.4000000000000146,
+ "255": 61.900000000000034,
+ "256": 41.250000000000085,
+ "257": -0.5000000000000142,
+ "258": -39.60000000000003,
+ "259": -90.00000000000003,
+ "260": -60.95,
+ "261": 16.349999999999998,
+ "262": 11.900000000000004,
+ "263": -2.949999999999963,
+ "264": 52.79999999999999,
+ "265": -40.9500000000001,
+ "266": -48.00000000000015,
+ "267": 80.69999999999999,
+ "268": -11.200000000000008,
+ "269": 55.49999999999989,
+ "270": 8.650000000000002,
+ "271": 7.000000000000008,
+ "272": -10.099999999999998,
+ "273": -18.40000000000002,
+ "274": 98.75000000000013,
+ "275": 50.89999999999986,
+ "276": 26.70000000000001,
+ "277": 24.149999999999967,
+ "278": -48.49999999999997,
+ "279": 12.249999999999915,
+ "280": -56.3,
+ "281": 27.75000000000001,
+ "282": 77.40000000000002,
+ "283": -52.050000000000075,
+ "284": -63.80000000000003,
+ "285": -53.15000000000003,
+ "286": 36.750000000000014,
+ "287": 73.64999999999995,
+ "288": 1.399999999999987,
+ "289": -18.59999999999998,
+ "290": 54.6500000000001,
+ "291": 92.6499999999999,
+ "292": -2.5999999999999965,
+ "293": -71.70000000000002,
+ "294": 66.90000000000002,
+ "295": 23.4,
+ "296": 13.749999999999973,
+ "297": 64.79999999999995,
+ "298": 55.49999999999994,
+ "299": 102.65000000000008,
+ "300": 14.349999999999994,
+ "301": 65.54999999999995,
+ "302": -23.200000000000006,
+ "303": 38.000000000000064,
+ "304": -14.300000000000002,
+ "305": 13.200000000000033,
+ "306": 96.90000000000005,
+ "307": 58.250000000000014,
+ "308": 78.40000000000003,
+ "309": 50.6,
+ "310": -15.800000000000066,
+ "311": 106.95000000000005,
+ "312": 76.39999999999998,
+ "313": 47.55000000000004,
+ "314": 44.74999999999998,
+ "315": -35.099999999999994,
+ "316": 46.999999999999886,
+ "317": 2.149999999999996,
+ "318": 47.699999999999896,
+ "319": 90.05000000000001,
+ "320": 38.899999999999864,
+ "321": -32.79999999999999,
+ "322": 11.65000000000003,
+ "323": 51.949999999999996,
+ "324": 42.699999999999996,
+ "325": 46.34999999999989,
+ "326": 105.25000000000001,
+ "327": 114.30000000000001,
+ "328": 103.14999999999999,
+ "329": 86.70000000000003,
+ "330": 16.149999999999967,
+ "331": -19.10000000000003,
+ "332": 36.300000000000026,
+ "333": 98.7500000000001,
+ "334": 60.249999999999986,
+ "335": 72.24999999999999,
+ "336": -6.950000000000001,
+ "337": 19.949999999999985,
+ "338": 79.65,
+ "339": 21.849999999999987,
+ "340": -63.19999999999994,
+ "341": 75.90000000000006,
+ "342": 102.65000000000003,
+ "343": 83.45000000000003,
+ "344": 93.25000000000001,
+ "345": 88.29999999999997,
+ "346": 80.69999999999999,
+ "347": 47.09999999999998,
+ "348": 89.45,
+ "349": 31.549999999999773,
+ "350": 50.099999999999966,
+ "351": 109.55000000000008,
+ "352": 101.45000000000006,
+ "353": 100.10000000000001,
+ "354": 53.39999999999997,
+ "355": 58.700000000000074,
+ "356": 118.55000000000004,
+ "357": 89.10000000000002,
+ "358": 103.34999999999994,
+ "359": 100.05000000000008,
+ "360": 86.35000000000002,
+ "361": 14.500000000000028,
+ "362": 102.50000000000009,
+ "363": 17.099999999999998,
+ "364": 104.3500000000001,
+ "365": 97.00000000000016,
+ "366": 64.19999999999989,
+ "367": 77.95000000000002,
+ "368": 114.90000000000002,
+ "369": 70.54999999999986,
+ "370": 103.85000000000005,
+ "371": 107.0500000000001,
+ "372": 108.70000000000005,
+ "373": 103.64999999999999,
+ "374": 68.89999999999998,
+ "375": 108.90000000000012,
+ "376": 36.84999999999999,
+ "377": 82.45000000000017,
+ "378": 67.19999999999996,
+ "379": 94.89999999999996,
+ "380": 109.6,
+ "381": 116.6000000000001,
+ "382": 88.05000000000007,
+ "383": 66.80000000000007,
+ "384": 42.199999999999925,
+ "385": 83.40000000000002,
+ "386": 85.20000000000003,
+ "387": 60.1499999999999,
+ "388": 118.30000000000003,
+ "389": 113.39999999999996,
+ "390": 116.75000000000004,
+ "391": 65.7999999999999,
+ "392": 81.84999999999992,
+ "393": 94.20000000000003,
+ "394": 111.90000000000015,
+ "395": 101.9000000000001,
+ "396": 71.54999999999998,
+ "397": 105.00000000000026,
+ "398": 91.35000000000012,
+ "399": 101.4,
+ "400": 118.05000000000013,
+ "401": 75.99999999999996,
+ "402": 86.80000000000008,
+ "403": 88.10000000000007,
+ "404": 86.90000000000005,
+ "405": 106.10000000000012,
+ "406": 80.30000000000005,
+ "407": 77.54999999999998,
+ "408": 115.70000000000022,
+ "409": 75.10000000000004,
+ "410": 65.99999999999997,
+ "411": 89.24999999999991,
+ "412": 117.2000000000001,
+ "413": 1.0499999999999994,
+ "414": 104.45000000000005,
+ "415": 97.60000000000004,
+ "416": 83.60000000000007,
+ "417": 102.25000000000017,
+ "418": 119.60000000000001,
+ "419": 57.40000000000003,
+ "420": 110.8000000000001,
+ "421": 105.85000000000002,
+ "422": 86.45000000000003,
+ "423": 98.09999999999995,
+ "424": 91.65000000000008,
+ "425": 102.65000000000005,
+ "426": 107.30000000000001,
+ "427": 101.99999999999996,
+ "428": 119.45000000000019,
+ "429": 112.50000000000009,
+ "430": 68.70000000000005,
+ "431": 75.10000000000004,
+ "432": 107.1500000000001,
+ "433": 102.35000000000022,
+ "434": 114.70000000000003,
+ "435": 99.05000000000008,
+ "436": 117.65000000000009,
+ "437": 80.44999999999996,
+ "438": 104.30000000000008,
+ "439": 84.70000000000005,
+ "440": 102.99999999999994,
+ "441": 115.99999999999996,
+ "442": 101.50000000000004,
+ "443": 118.55000000000007,
+ "444": 116.45000000000012,
+ "445": 100.45000000000006,
+ "446": 81.90000000000018,
+ "447": 107.70000000000002,
+ "448": 109.80000000000008,
+ "449": 90.55000000000014,
+ "450": 118.55000000000013,
+ "451": 116.35000000000004,
+ "452": 110.55000000000011,
+ "453": 117.85000000000008,
+ "454": 66.59999999999994,
+ "455": 99.1500000000001,
+ "456": 107.90000000000009,
+ "457": 110.05000000000011,
+ "458": 117.25000000000011,
+ "459": 100.7000000000001,
+ "460": 112.10000000000007,
+ "461": 115.49999999999996,
+ "462": 107.40000000000013,
+ "463": 113.90000000000013,
+ "464": 111.44999999999995,
+ "465": 112.85000000000016,
+ "466": 120.10000000000005,
+ "467": 103.40000000000002,
+ "468": 84.40000000000015,
+ "469": 118.70000000000007,
+ "470": 106.20000000000016,
+ "471": 118.60000000000005,
+ "472": 80.49999999999991,
+ "473": 104.3500000000001,
+ "474": 62.599999999999795,
+ "475": 100.14999999999988,
+ "476": 111.25,
+ "477": 90.34999999999998,
+ "478": 111.2,
+ "479": 103.05000000000008,
+ "480": 108.60000000000007,
+ "481": 94.9500000000001,
+ "482": 108.60000000000001,
+ "483": 111.3000000000001,
+ "484": 105.14999999999999,
+ "485": 107.89999999999998,
+ "486": 112.00000000000004,
+ "487": 102.50000000000011,
+ "488": 114.50000000000009,
+ "489": 117.50000000000003,
+ "490": 107.95000000000005,
+ "491": 118.10000000000004,
+ "492": 108.85000000000011,
+ "493": 90.69999999999989,
+ "494": 113.45000000000012,
+ "495": 111.80000000000005,
+ "496": 102.4500000000001,
+ "497": 103.65000000000002,
+ "498": 118.40000000000018,
+ "499": 115.05000000000014,
+ "500": 107.10000000000012,
+ "501": 104.65000000000006,
+ "502": 110.80000000000013,
+ "503": 116.65000000000013,
+ "504": 113.25000000000016,
+ "505": 119.95000000000003,
+ "506": 113.99999999999997,
+ "507": 111.40000000000012,
+ "508": 111.50000000000009,
+ "509": 94.70000000000005,
+ "510": 107.7000000000001,
+ "511": 109.80000000000007,
+ "512": 117.85000000000011,
+ "513": 50.04999999999987,
+ "514": 106.19999999999999,
+ "515": 100.60000000000014,
+ "516": 103.40000000000008,
+ "517": 107.45000000000007,
+ "518": 115.55000000000005,
+ "519": 122.30000000000011,
+ "520": 113.9000000000001,
+ "521": 109.85000000000022,
+ "522": 58.79999999999984,
+ "523": 109.60000000000002,
+ "524": 109.25000000000001,
+ "525": 113.55000000000008,
+ "526": -18.29999999999997,
+ "527": 117.75000000000014,
+ "528": 112.20000000000006,
+ "529": 109.3,
+ "530": 106.95000000000007,
+ "531": 109.20000000000007,
+ "532": 118.65000000000028,
+ "533": 109.25000000000004,
+ "534": 110.70000000000003,
+ "535": 105.49999999999987,
+ "536": 100.99999999999993,
+ "537": 107.05000000000013,
+ "538": 99.2000000000001,
+ "539": 96.35000000000004,
+ "540": 100.59999999999998,
+ "541": 114.40000000000003,
+ "542": 118.9500000000001,
+ "543": 110.50000000000001,
+ "544": 116.80000000000011,
+ "545": 119.30000000000018,
+ "546": 110.94999999999997,
+ "547": 111.75000000000014,
+ "548": 100.40000000000006,
+ "549": 113.80000000000011,
+ "550": 103.20000000000013,
+ "551": 104.20000000000002,
+ "552": 116.14999999999998,
+ "553": 103.15000000000009,
+ "554": 109.00000000000001,
+ "555": 88.94999999999992,
+ "556": 112.75000000000013,
+ "557": 116.65000000000029,
+ "558": 112.65000000000002,
+ "559": 13.900000000000045,
+ "560": 105.8000000000001,
+ "561": 115.10000000000015,
+ "562": 105.99999999999997,
+ "563": 109.1000000000002,
+ "564": 105.3,
+ "565": 97.60000000000012,
+ "566": 65.04999999999978,
+ "567": 109.5500000000002,
+ "568": 105.40000000000009,
+ "569": 113.2000000000001,
+ "570": 118.25000000000006,
+ "571": -6.449999999999994,
+ "572": 110.54999999999994,
+ "573": 103.25000000000009,
+ "574": 104.44999999999997,
+ "575": 103.24999999999999,
+ "576": 90.55000000000003,
+ "577": 116.3000000000001,
+ "578": 102.20000000000014,
+ "579": 111.30000000000008,
+ "580": 101.3999999999999,
+ "581": 118.05000000000013,
+ "582": 96.99999999999996,
+ "583": 96.95,
+ "584": 112.85000000000008,
+ "585": 99.14999999999992,
+ "586": 109.0500000000001,
+ "587": 103.90000000000008,
+ "588": 107.29999999999998,
+ "589": 111.85000000000015,
+ "590": 105.75000000000016,
+ "591": 110.60000000000016,
+ "592": 105.80000000000013,
+ "593": 108.15000000000002,
+ "594": 115.10000000000002,
+ "595": 111.00000000000003,
+ "596": 102.49999999999994,
+ "597": 104.94999999999996,
+ "598": 115.60000000000012,
+ "599": 109.90000000000016,
+ "600": 115.2000000000001,
+ "601": 118.10000000000028,
+ "602": 112.0,
+ "603": 111.90000000000013,
+ "604": 107.69999999999993,
+ "605": 110.35000000000011,
+ "606": 119.05000000000005,
+ "607": 101.09999999999991,
+ "608": 106.10000000000005,
+ "609": 114.89999999999999,
+ "610": 116.1000000000001,
+ "611": 115.60000000000007,
+ "612": 119.10000000000002,
+ "613": 112.6500000000001,
+ "614": 110.95000000000007,
+ "615": 113.95000000000024,
+ "616": 116.7000000000001,
+ "617": -14.399999999999974,
+ "618": 112.20000000000005,
+ "619": 111.85000000000002,
+ "620": 117.75000000000013,
+ "621": 103.20000000000016,
+ "622": 115.59999999999998,
+ "623": 113.70000000000017,
+ "624": 5.450000000000024,
+ "625": 100.35000000000002,
+ "626": 114.05000000000001,
+ "627": 111.54999999999998,
+ "628": 110.35000000000002,
+ "629": 110.10000000000015,
+ "630": 106.50000000000004,
+ "631": 107.24999999999997,
+ "632": 111.90000000000015,
+ "633": 45.49999999999989,
+ "634": 112.14999999999999,
+ "635": 98.5000000000002,
+ "636": 118.90000000000002,
+ "637": 107.85000000000004,
+ "638": 110.99999999999994,
+ "639": 104.90000000000008,
+ "640": 113.55000000000008,
+ "641": 106.25000000000004,
+ "642": 111.25000000000011,
+ "643": 103.25000000000013,
+ "644": 98.44999999999997,
+ "645": 109.50000000000004,
+ "646": 99.8499999999998,
+ "647": 110.80000000000015,
+ "648": 101.65000000000018,
+ "649": 106.80000000000013,
+ "650": 111.8500000000001,
+ "651": 109.15000000000005,
+ "652": 109.45000000000009,
+ "653": 104.00000000000013,
+ "654": 103.70000000000003,
+ "655": 115.25000000000004,
+ "656": 100.90000000000002,
+ "657": 101.54999999999995,
+ "658": 114.55000000000007,
+ "659": 116.80000000000007,
+ "660": 113.50000000000001,
+ "661": 115.2,
+ "662": 111.30000000000021,
+ "663": 113.45000000000009,
+ "664": 100.39999999999992,
+ "665": 114.0000000000001,
+ "666": 114.50000000000009,
+ "667": 104.40000000000018,
+ "668": 113.00000000000009,
+ "669": 110.95000000000014,
+ "670": 101.69999999999992,
+ "671": 119.85000000000001,
+ "672": 109.60000000000004,
+ "673": 105.94999999999993,
+ "674": 108.05000000000004,
+ "675": 114.35000000000005,
+ "676": 114.20000000000007,
+ "677": 110.75,
+ "678": 101.55000000000013,
+ "679": 113.1000000000001,
+ "680": 115.05000000000004,
+ "681": 101.35000000000012,
+ "682": 122.35000000000012,
+ "683": 109.4000000000001,
+ "684": 112.55000000000027,
+ "685": 117.75000000000003,
+ "686": 110.04999999999998,
+ "687": 106.49999999999996,
+ "688": 112.20000000000014,
+ "689": 107.24999999999997,
+ "690": 108.20000000000006,
+ "691": 107.94999999999995,
+ "692": 97.10000000000002,
+ "693": 113.2500000000001,
+ "694": 72.49999999999993,
+ "695": 111.00000000000009,
+ "696": 108.40000000000002,
+ "697": 110.40000000000003,
+ "698": 118.35000000000007,
+ "699": 109.55000000000011,
+ "700": 100.65000000000006,
+ "701": 112.15,
+ "702": 110.30000000000018,
+ "703": 108.30000000000004,
+ "704": 113.9000000000001,
+ "705": 114.40000000000022,
+ "706": 113.80000000000008,
+ "707": 107.00000000000006,
+ "708": 114.30000000000013,
+ "709": 111.5,
+ "710": 86.9000000000001,
+ "711": 85.55000000000007,
+ "712": 113.85000000000005,
+ "713": 105.90000000000009,
+ "714": 100.50000000000006,
+ "715": 103.79999999999994,
+ "716": 111.40000000000009,
+ "717": 107.15000000000002,
+ "718": 117.35000000000012,
+ "719": 109.5,
+ "720": 112.84999999999987,
+ "721": 115.7500000000001,
+ "722": 109.19999999999999,
+ "723": 111.45000000000006,
+ "724": 117.95000000000005,
+ "725": 114.35000000000012,
+ "726": 116.25000000000016,
+ "727": 114.70000000000003,
+ "728": 111.6000000000001,
+ "729": 110.75000000000016,
+ "730": 114.95,
+ "731": 110.25000000000003,
+ "732": 102.05,
+ "733": 105.09999999999987,
+ "734": 107.95000000000009,
+ "735": 107.90000000000009,
+ "736": 114.10000000000001,
+ "737": 107.64999999999995,
+ "738": 113.10000000000018,
+ "739": 106.10000000000016,
+ "740": 105.20000000000005,
+ "741": 110.35000000000015,
+ "742": 93.35000000000001,
+ "743": 114.70000000000013,
+ "744": 107.29999999999981,
+ "745": 104.05000000000011,
+ "746": 111.55000000000003,
+ "747": 114.00000000000013,
+ "748": 108.75000000000004,
+ "749": 117.49999999999994,
+ "750": 89.80000000000007,
+ "751": 102.75000000000017,
+ "752": 118.40000000000008,
+ "753": 113.15000000000008,
+ "754": 111.35000000000002,
+ "755": 111.75000000000013,
+ "756": 114.25000000000013,
+ "757": 119.44999999999997,
+ "758": 104.90000000000012,
+ "759": 113.25000000000004,
+ "760": 112.05000000000004,
+ "761": 114.40000000000018,
+ "762": 110.40000000000005,
+ "763": 113.0500000000001,
+ "764": 107.45000000000003,
+ "765": 105.15000000000013,
+ "766": 115.90000000000006,
+ "767": 114.45000000000016,
+ "768": 107.40000000000006,
+ "769": 113.75000000000011,
+ "770": 115.80000000000013,
+ "771": 105.60000000000005,
+ "772": 109.69999999999997,
+ "773": 107.4500000000001,
+ "774": 116.75000000000007,
+ "775": 115.25000000000016,
+ "776": 103.55000000000005,
+ "777": 118.75000000000003,
+ "778": 110.1500000000002,
+ "779": 105.20000000000002,
+ "780": 111.55000000000011,
+ "781": 111.25000000000003,
+ "782": 104.10000000000004,
+ "783": 112.60000000000004,
+ "784": 114.30000000000003,
+ "785": 113.30000000000013,
+ "786": 110.45000000000002,
+ "787": 110.20000000000026,
+ "788": 111.50000000000003,
+ "789": 102.80000000000021,
+ "790": 113.90000000000013,
+ "791": 110.20000000000017,
+ "792": 104.94999999999985,
+ "793": 109.85000000000015,
+ "794": 113.50000000000011,
+ "795": 113.99999999999997,
+ "796": 114.85000000000012,
+ "797": 114.30000000000008,
+ "798": 104.95,
+ "799": 102.60000000000007,
+ "800": 112.60000000000005,
+ "801": 110.15000000000012,
+ "802": 122.20000000000013,
+ "803": 106.79999999999993,
+ "804": 107.14999999999999,
+ "805": 115.70000000000029,
+ "806": 111.9999999999999,
+ "807": 107.59999999999991,
+ "808": 105.20000000000017,
+ "809": 107.95000000000002,
+ "810": 115.2500000000001,
+ "811": 108.95000000000016,
+ "812": 114.10000000000004,
+ "813": 110.25000000000003,
+ "814": 102.10000000000005,
+ "815": 119.60000000000014,
+ "816": 109.00000000000006,
+ "817": 105.50000000000004,
+ "818": 109.3000000000002,
+ "819": 122.05000000000007,
+ "820": 103.55000000000001,
+ "821": 113.19999999999997,
+ "822": 108.9000000000001,
+ "823": 119.45000000000012,
+ "824": 104.34999999999988,
+ "825": 107.30000000000013,
+ "826": 109.25000000000001,
+ "827": 101.7,
+ "828": 107.75,
+ "829": 99.14999999999996,
+ "830": 111.55000000000017,
+ "831": 107.64999999999992,
+ "832": 118.00000000000004,
+ "833": 108.14999999999995,
+ "834": 108.60000000000016,
+ "835": 107.50000000000004,
+ "836": -39.44999999999998,
+ "837": 103.6,
+ "838": 105.15000000000013,
+ "839": 112.25000000000009,
+ "840": 109.80000000000011,
+ "841": 112.65000000000006,
+ "842": 113.40000000000003,
+ "843": 111.70000000000003,
+ "844": 113.70000000000019,
+ "845": 114.40000000000018,
+ "846": 115.1000000000002,
+ "847": 113.15000000000018,
+ "848": 110.15000000000012,
+ "849": 114.65,
+ "850": 110.5500000000001,
+ "851": 108.00000000000004,
+ "852": 116.00000000000006,
+ "853": 121.59999999999997,
+ "854": 111.44999999999999,
+ "855": 105.99999999999984,
+ "856": 105.05000000000001,
+ "857": 113.05000000000022,
+ "858": 93.14999999999999,
+ "859": 103.90000000000002,
+ "860": 114.00000000000018,
+ "861": 105.20000000000002,
+ "862": 119.0,
+ "863": 109.30000000000017,
+ "864": 30.150000000000077,
+ "865": 104.75000000000021,
+ "866": 107.74999999999987,
+ "867": 108.65000000000009,
+ "868": 106.0500000000001,
+ "869": 111.90000000000003,
+ "870": 114.20000000000013,
+ "871": 62.35000000000002,
+ "872": 111.50000000000018,
+ "873": 106.54999999999993,
+ "874": 111.14999999999999,
+ "875": 17.399999999999903,
+ "876": 108.74999999999993,
+ "877": 95.84999999999992,
+ "878": 118.00000000000006,
+ "879": 108.85000000000004,
+ "880": 121.15000000000005,
+ "881": 55.79999999999997,
+ "882": 111.30000000000013,
+ "883": 116.30000000000017,
+ "884": 98.75000000000006,
+ "885": 107.64999999999998,
+ "886": 110.45000000000007,
+ "887": 109.75,
+ "888": 114.25000000000004,
+ "889": 116.0000000000001,
+ "890": 114.55000000000021,
+ "891": 115.80000000000013,
+ "892": 120.35000000000015,
+ "893": 113.35000000000025,
+ "894": 112.35000000000002,
+ "895": 115.10000000000012,
+ "896": 108.95,
+ "897": 107.90000000000012,
+ "898": 109.5500000000001,
+ "899": 111.40000000000002,
+ "900": 113.60000000000005,
+ "901": 119.90000000000006,
+ "902": 111.40000000000008,
+ "903": 109.85000000000002,
+ "904": 107.05000000000004,
+ "905": 110.95000000000002,
+ "906": 104.25000000000011,
+ "907": 115.45000000000007,
+ "908": 111.35000000000007,
+ "909": 113.00000000000007,
+ "910": 100.75,
+ "911": 107.25000000000006,
+ "912": 116.50000000000011,
+ "913": 115.50000000000007,
+ "914": 119.75000000000024,
+ "915": 110.95000000000005,
+ "916": 113.60000000000008,
+ "917": 115.1500000000001,
+ "918": 108.45000000000014,
+ "919": 105.50000000000004,
+ "920": 108.60000000000004,
+ "921": 110.95000000000009,
+ "922": 106.90000000000013,
+ "923": 109.70000000000006,
+ "924": 111.69999999999999,
+ "925": 99.10000000000014,
+ "926": 103.05000000000014,
+ "927": 106.70000000000009,
+ "928": 108.35000000000005,
+ "929": 119.50000000000003,
+ "930": 108.15000000000006,
+ "931": 106.85000000000014,
+ "932": 107.50000000000004,
+ "933": 112.35000000000014,
+ "934": 102.64999999999996,
+ "935": 111.8999999999999,
+ "936": 109.4999999999999,
+ "937": 104.24999999999994,
+ "938": 109.80000000000011,
+ "939": 107.1500000000001,
+ "940": 114.45000000000009,
+ "941": 20.749999999999954,
+ "942": 113.25000000000004,
+ "943": 124.00000000000014,
+ "944": 112.45000000000024,
+ "945": 114.70000000000006,
+ "946": 116.30000000000015,
+ "947": 106.39999999999992,
+ "948": 104.89999999999999,
+ "949": 107.8500000000001,
+ "950": 108.95000000000006,
+ "951": 116.10000000000011,
+ "952": 104.64999999999992,
+ "953": 110.35,
+ "954": 113.50000000000001,
+ "955": 101.50000000000003,
+ "956": 111.65000000000008,
+ "957": 103.49999999999997,
+ "958": 115.75,
+ "959": 100.75000000000014,
+ "960": 104.35000000000005,
+ "961": 110.40000000000006,
+ "962": 110.10000000000002,
+ "963": 108.3000000000001,
+ "964": 108.7500000000001,
+ "965": 105.70000000000012,
+ "966": 108.80000000000017,
+ "967": 107.90000000000019,
+ "968": 117.20000000000012,
+ "969": 100.4,
+ "970": 124.4,
+ "971": 93.1000000000001,
+ "972": 111.25000000000013,
+ "973": 110.79999999999998,
+ "974": 117.05000000000001,
+ "975": 118.20000000000016,
+ "976": 108.64999999999999,
+ "977": 111.25000000000018,
+ "978": 109.4000000000002,
+ "979": 118.40000000000002,
+ "980": 114.20000000000019,
+ "981": 113.50000000000003,
+ "982": 115.45,
+ "983": 101.25000000000014,
+ "984": 112.84999999999997,
+ "985": 117.15000000000013,
+ "986": 113.05000000000013,
+ "987": 119.1500000000001,
+ "988": 104.25000000000007,
+ "989": 104.00000000000004,
+ "990": 117.00000000000009,
+ "991": 113.70000000000003,
+ "992": 113.20000000000013,
+ "993": 109.2500000000001,
+ "994": 118.40000000000006,
+ "995": 105.75000000000011,
+ "996": -74.0,
+ "997": 111.94999999999999,
+ "998": 110.85,
+ "999": 107.6000000000001,
+ "1000": 108.75000000000003
+ },
+ "2": {
+ "1": -60.0500000000001,
+ "2": -52.65000000000007,
+ "3": -21.20000000000001,
+ "4": -15.14999999999997,
+ "5": -18.24999999999997,
+ "6": -18.799999999999965,
+ "7": -13.49999999999999,
+ "8": -9.349999999999989,
+ "9": -11.749999999999988,
+ "10": -73.45000000000002,
+ "11": -16.54999999999997,
+ "12": -80.30000000000001,
+ "13": -13.34999999999999,
+ "14": -20.149999999999988,
+ "15": -16.399999999999974,
+ "16": -37.60000000000002,
+ "17": -47.55000000000007,
+ "18": -19.849999999999962,
+ "19": -10.750000000000005,
+ "20": -53.200000000000074,
+ "21": -104.7,
+ "22": -13.649999999999968,
+ "23": -95.75,
+ "24": -5.299999999999985,
+ "25": -21.349999999999966,
+ "26": -86.05000000000001,
+ "27": -18.699999999999967,
+ "28": -14.249999999999979,
+ "29": -22.749999999999954,
+ "30": -74.00000000000001,
+ "31": -15.749999999999975,
+ "32": -58.75000000000009,
+ "33": -4.64999999999998,
+ "34": -21.949999999999942,
+ "35": -18.999999999999964,
+ "36": -22.850000000000026,
+ "37": -18.549999999999965,
+ "38": -16.949999999999974,
+ "39": -23.44999999999995,
+ "40": -20.699999999999974,
+ "41": -60.850000000000044,
+ "42": -12.499999999999986,
+ "43": -16.849999999999984,
+ "44": -12.94999999999999,
+ "45": -88.6,
+ "46": -18.349999999999966,
+ "47": -93.39999999999995,
+ "48": -8.999999999999993,
+ "49": -16.299999999999976,
+ "50": -42.65000000000005,
+ "51": -15.99999999999998,
+ "52": -34.4,
+ "53": -15.799999999999974,
+ "54": -30.149999999999977,
+ "55": -42.45000000000004,
+ "56": -31.99999999999998,
+ "57": -15.049999999999981,
+ "58": -17.349999999999973,
+ "59": -63.7,
+ "60": -24.44999999999995,
+ "61": -88.1,
+ "62": 9.500000000000004,
+ "63": -83.4500000000001,
+ "64": -17.949999999999985,
+ "65": -16.549999999999976,
+ "66": -10.499999999999996,
+ "67": -18.04999999999997,
+ "68": -6.349999999999986,
+ "69": -100.85,
+ "70": 4.749999999999992,
+ "71": -41.45000000000006,
+ "72": -19.649999999999963,
+ "73": -32.05000000000003,
+ "74": -21.449999999999964,
+ "75": -15.999999999999977,
+ "76": -45.150000000000055,
+ "77": -34.35000000000006,
+ "78": -49.85000000000007,
+ "79": -34.950000000000024,
+ "80": -13.199999999999985,
+ "81": -16.69999999999997,
+ "82": -15.299999999999969,
+ "83": -96.65,
+ "84": -4.099999999999989,
+ "85": -16.19999999999996,
+ "86": -35.35000000000004,
+ "87": -84.80000000000001,
+ "88": -61.3500000000001,
+ "89": -12.599999999999987,
+ "90": -96.25,
+ "91": -19.599999999999966,
+ "92": 15.80000000000003,
+ "93": -15.699999999999976,
+ "94": -17.499999999999975,
+ "95": -97.2,
+ "96": -37.15000000000004,
+ "97": -12.400000000000004,
+ "98": -68.80000000000005,
+ "99": -2.449999999999953,
+ "100": -10.450000000000008,
+ "101": 13.75000000000006,
+ "102": -6.599999999999993,
+ "103": -22.99999999999995,
+ "104": -65.05,
+ "105": -12.34999999999999,
+ "106": -1.4499999999999758,
+ "107": -60.35000000000007,
+ "108": -60.35000000000002,
+ "109": -9.999999999999991,
+ "110": -92.65,
+ "111": -32.95000000000004,
+ "112": -38.100000000000044,
+ "113": -20.94999999999996,
+ "114": -17.84999999999997,
+ "115": -16.899999999999974,
+ "116": 16.00000000000003,
+ "117": -70.19999999999999,
+ "118": -5.849999999999993,
+ "119": -31.55,
+ "120": -17.449999999999974,
+ "121": -79.99999999999997,
+ "122": -70.4000000000001,
+ "123": -5.249999999999984,
+ "124": -1.099999999999973,
+ "125": -44.84999999999998,
+ "126": -12.549999999999992,
+ "127": -71.15000000000002,
+ "128": -12.849999999999973,
+ "129": -6.9999999999999964,
+ "130": -58.899999999999984,
+ "131": 4.600000000000029,
+ "132": -4.5,
+ "133": -44.7,
+ "134": 8.050000000000043,
+ "135": -13.849999999999984,
+ "136": -84.0,
+ "137": -7.149999999999976,
+ "138": -15.449999999999976,
+ "139": -0.6499999999999901,
+ "140": -104.39999999999996,
+ "141": -12.699999999999994,
+ "142": -16.849999999999966,
+ "143": -42.65000000000007,
+ "144": -8.099999999999996,
+ "145": -16.29999999999997,
+ "146": 1.1000000000000354,
+ "147": 3.400000000000049,
+ "148": 19.6499999999999,
+ "149": -11.59999999999999,
+ "150": 8.600000000000005,
+ "151": -20.24999999999996,
+ "152": -89.3,
+ "153": -7.799999999999988,
+ "154": -19.74999999999996,
+ "155": -24.299999999999947,
+ "156": -10.549999999999986,
+ "157": 2.708944180085382e-14,
+ "158": -102.1,
+ "159": -9.149999999999999,
+ "160": -87.75000000000001,
+ "161": -14.699999999999983,
+ "162": -7.6999999999999895,
+ "163": -1.9499999999999993,
+ "164": -18.499999999999982,
+ "165": -31.299999999999976,
+ "166": -83.59999999999998,
+ "167": 4.950000000000003,
+ "168": -31.099999999999966,
+ "169": -16.649999999999963,
+ "170": -11.100000000000001,
+ "171": -42.499999999999986,
+ "172": -4.499999999999991,
+ "173": -3.4500000000000055,
+ "174": -11.299999999999994,
+ "175": -26.749999999999943,
+ "176": -16.39999999999997,
+ "177": -15.399999999999986,
+ "178": -12.199999999999974,
+ "179": -71.8,
+ "180": -16.399999999999974,
+ "181": -22.249999999999993,
+ "182": -5.899999999999976,
+ "183": -15.549999999999962,
+ "184": 3.05,
+ "185": 7.4000000000000234,
+ "186": -18.04999999999997,
+ "187": -2.5499999999999687,
+ "188": -11.799999999999988,
+ "189": 10.7,
+ "190": -6.700000000000002,
+ "191": -2.199999999999967,
+ "192": -7.3499999999999925,
+ "193": -3.9499999999999966,
+ "194": -5.39999999999999,
+ "195": -4.99999999999999,
+ "196": -9.950000000000006,
+ "197": -10.499999999999986,
+ "198": -7.949999999999985,
+ "199": -7.899999999999998,
+ "200": -19.149999999999967,
+ "201": -16.549999999999976,
+ "202": 12.000000000000002,
+ "203": 4.300000000000029,
+ "204": -53.09999999999999,
+ "205": -89.29999999999998,
+ "206": -20.349999999999962,
+ "207": -20.850000000000023,
+ "208": -25.250000000000014,
+ "209": -3.6999999999999886,
+ "210": -26.000000000000018,
+ "211": -6.149999999999986,
+ "212": 7.849999999999983,
+ "213": 10.099999999999941,
+ "214": 9.800000000000013,
+ "215": -56.39999999999997,
+ "216": 18.049999999999997,
+ "217": -9.949999999999992,
+ "218": -22.549999999999955,
+ "219": -58.94999999999998,
+ "220": -1.6999999999999675,
+ "221": 19.2,
+ "222": -96.24999999999999,
+ "223": -17.59999999999997,
+ "224": -17.09999999999997,
+ "225": -49.849999999999966,
+ "226": -8.65,
+ "227": -5.8500000000000005,
+ "228": 27.199999999999836,
+ "229": -22.29999999999995,
+ "230": 47.09999999999978,
+ "231": 38.24999999999979,
+ "232": -23.84999999999996,
+ "233": -20.04999999999996,
+ "234": 18.100000000000062,
+ "235": -83.6,
+ "236": -1.749999999999967,
+ "237": 50.64999999999979,
+ "238": -13.399999999999984,
+ "239": -33.30000000000001,
+ "240": 18.599999999999998,
+ "241": 9.900000000000016,
+ "242": -43.25000000000004,
+ "243": -75.45,
+ "244": -11.850000000000032,
+ "245": -5.199999999999981,
+ "246": -18.899999999999952,
+ "247": 32.04999999999998,
+ "248": 92.45000000000007,
+ "249": 37.34999999999985,
+ "250": -9.349999999999985,
+ "251": 41.24999999999984,
+ "252": -4.4999999999999805,
+ "253": 66.94999999999992,
+ "254": -78.1999999999999,
+ "255": -38.05,
+ "256": -41.100000000000065,
+ "257": -11.800000000000004,
+ "258": 12.700000000000077,
+ "259": 81.5,
+ "260": -13.00000000000001,
+ "261": 10.350000000000037,
+ "262": -6.049999999999979,
+ "263": 40.89999999999989,
+ "264": -21.799999999999997,
+ "265": -64.3,
+ "266": -70.15000000000003,
+ "267": 23.35000000000007,
+ "268": -2.9499999999999975,
+ "269": 13.800000000000047,
+ "270": 54.799999999999784,
+ "271": -77.69999999999999,
+ "272": -10.550000000000008,
+ "273": -1.1499999999999932,
+ "274": 94.7000000000002,
+ "275": -17.599999999999966,
+ "276": 12.500000000000046,
+ "277": 16.050000000000054,
+ "278": 39.399999999999885,
+ "279": 3.550000000000023,
+ "280": 69.8500000000001,
+ "281": 1.200000000000074,
+ "282": -48.99999999999997,
+ "283": -5.4500000000000055,
+ "284": -23.400000000000023,
+ "285": 98.65000000000018,
+ "286": 38.44999999999977,
+ "287": -6.950000000000058,
+ "288": 63.94999999999974,
+ "289": 117.30000000000028,
+ "290": 38.149999999999736,
+ "291": 10.900000000000109,
+ "292": 112.65000000000029,
+ "293": -12.149999999999991,
+ "294": 70.55000000000007,
+ "295": 70.34999999999997,
+ "296": 39.69999999999973,
+ "297": 34.79999999999984,
+ "298": -16.19999999999996,
+ "299": -1.8499999999999632,
+ "300": 75.50000000000023,
+ "301": 109.74999999999983,
+ "302": 17.300000000000008,
+ "303": -14.399999999999961,
+ "304": 68.49999999999991,
+ "305": 41.199999999999974,
+ "306": 56.44999999999981,
+ "307": 39.44999999999979,
+ "308": 12.950000000000053,
+ "309": 21.300000000000033,
+ "310": 46.99999999999983,
+ "311": 15.049999999999995,
+ "312": -19.35000000000003,
+ "313": 19.849999999999966,
+ "314": 24.099999999999923,
+ "315": 7.9000000000000155,
+ "316": -3.5499999999999767,
+ "317": 51.949999999999825,
+ "318": 95.50000000000017,
+ "319": 117.35000000000028,
+ "320": 49.4499999999998,
+ "321": 96.89999999999999,
+ "322": 26.549999999999972,
+ "323": 82.90000000000013,
+ "324": 30.44999999999996,
+ "325": 1.9000000000000332,
+ "326": 77.85000000000002,
+ "327": -72.95,
+ "328": 10.55000000000005,
+ "329": 29.100000000000016,
+ "330": -1.749999999999986,
+ "331": 17.25000000000002,
+ "332": -83.45,
+ "333": 27.74999999999983,
+ "334": 46.699999999999946,
+ "335": 23.499999999999975,
+ "336": 76.55000000000004,
+ "337": 18.050000000000004,
+ "338": 40.60000000000001,
+ "339": -0.4500000000000075,
+ "340": 34.799999999999976,
+ "341": 20.250000000000043,
+ "342": 82.40000000000002,
+ "343": 63.749999999999865,
+ "344": 62.84999999999989,
+ "345": -52.50000000000004,
+ "346": 113.95000000000022,
+ "347": 114.10000000000022,
+ "348": 94.75000000000013,
+ "349": 108.35000000000025,
+ "350": 103.55000000000015,
+ "351": 112.00000000000016,
+ "352": 95.20000000000013,
+ "353": 104.2000000000001,
+ "354": 115.90000000000009,
+ "355": 78.20000000000019,
+ "356": 77.29999999999978,
+ "357": 97.55000000000021,
+ "358": 102.70000000000017,
+ "359": -34.400000000000006,
+ "360": 59.949999999999754,
+ "361": 38.94999999999976,
+ "362": 8.149999999999974,
+ "363": 106.90000000000023,
+ "364": -16.400000000000006,
+ "365": 109.4500000000002,
+ "366": 67.8500000000002,
+ "367": 109.25000000000023,
+ "368": 99.60000000000008,
+ "369": 107.45000000000022,
+ "370": -28.1,
+ "371": 98.55000000000003,
+ "372": 103.85000000000024,
+ "373": 60.59999999999992,
+ "374": 96.85000000000015,
+ "375": 69.14999999999986,
+ "376": 80.15000000000003,
+ "377": 83.60000000000002,
+ "378": 69.89999999999993,
+ "379": 48.349999999999866,
+ "380": 41.89999999999981,
+ "381": 111.3000000000002,
+ "382": 80.39999999999985,
+ "383": 108.60000000000012,
+ "384": 75.60000000000004,
+ "385": 73.29999999999995,
+ "386": 70.69999999999995,
+ "387": 36.15000000000002,
+ "388": 114.00000000000017,
+ "389": 90.70000000000012,
+ "390": 100.60000000000001,
+ "391": 95.10000000000014,
+ "392": 58.199999999999925,
+ "393": 83.75000000000004,
+ "394": 91.40000000000008,
+ "395": 102.14999999999995,
+ "396": 72.14999999999993,
+ "397": 79.10000000000002,
+ "398": 97.34999999999984,
+ "399": 46.89999999999989,
+ "400": 109.50000000000004,
+ "401": 93.5000000000001,
+ "402": 52.04999999999983,
+ "403": 88.0,
+ "404": 94.50000000000006,
+ "405": 82.25,
+ "406": 80.64999999999992,
+ "407": 70.4500000000002,
+ "408": 83.90000000000006,
+ "409": 113.60000000000016,
+ "410": 114.35000000000024,
+ "411": 106.05000000000022,
+ "412": 89.89999999999993,
+ "413": 34.84999999999998,
+ "414": 113.10000000000022,
+ "415": 87.05000000000004,
+ "416": 107.09999999999988,
+ "417": 100.45000000000009,
+ "418": 94.6500000000001,
+ "419": 89.29999999999987,
+ "420": 99.95000000000017,
+ "421": 112.05000000000015,
+ "422": 72.54999999999987,
+ "423": 117.7000000000002,
+ "424": 81.00000000000001,
+ "425": 109.80000000000011,
+ "426": 110.59999999999997,
+ "427": 78.0,
+ "428": 89.65000000000006,
+ "429": 32.34999999999974,
+ "430": 110.75000000000009,
+ "431": 113.1000000000001,
+ "432": 100.60000000000004,
+ "433": 105.1000000000001,
+ "434": 115.55000000000014,
+ "435": 87.00000000000001,
+ "436": 107.34999999999987,
+ "437": 109.20000000000006,
+ "438": 96.19999999999997,
+ "439": 29.500000000000064,
+ "440": 113.40000000000023,
+ "441": 112.65000000000028,
+ "442": 114.2000000000002,
+ "443": 113.60000000000024,
+ "444": 103.9500000000002,
+ "445": 114.55000000000022,
+ "446": 106.99999999999997,
+ "447": 89.10000000000008,
+ "448": 113.35000000000004,
+ "449": 92.6000000000001,
+ "450": -1.3000000000000105,
+ "451": 95.55000000000005,
+ "452": 90.25000000000004,
+ "453": 108.1000000000002,
+ "454": 111.75000000000027,
+ "455": 101.15000000000012,
+ "456": 100.40000000000015,
+ "457": -16.650000000000052,
+ "458": 105.79999999999986,
+ "459": 110.05000000000003,
+ "460": 103.6000000000001,
+ "461": 69.54999999999978,
+ "462": 109.0999999999999,
+ "463": 98.35000000000005,
+ "464": 81.55,
+ "465": 103.65000000000012,
+ "466": 55.249999999999844,
+ "467": 99.00000000000013,
+ "468": 102.70000000000007,
+ "469": 72.60000000000021,
+ "470": 113.60000000000026,
+ "471": 110.14999999999993,
+ "472": 94.29999999999973,
+ "473": 99.04999999999987,
+ "474": 112.35000000000012,
+ "475": 113.90000000000016,
+ "476": -11.79999999999998,
+ "477": 93.74999999999982,
+ "478": 56.59999999999996,
+ "479": 115.30000000000025,
+ "480": 98.35000000000002,
+ "481": 107.59999999999978,
+ "482": 104.40000000000003,
+ "483": 103.55000000000005,
+ "484": 11.849999999999994,
+ "485": 46.649999999999885,
+ "486": 109.40000000000019,
+ "487": 107.5500000000002,
+ "488": 40.000000000000014,
+ "489": 77.29999999999984,
+ "490": 64.79999999999984,
+ "491": 106.04999999999991,
+ "492": 108.59999999999985,
+ "493": 92.74999999999999,
+ "494": 102.60000000000015,
+ "495": -18.599999999999998,
+ "496": 62.79999999999974,
+ "497": 111.74999999999993,
+ "498": 114.00000000000016,
+ "499": -33.450000000000024,
+ "500": 108.90000000000008,
+ "501": 108.55000000000004,
+ "502": 109.09999999999994,
+ "503": 110.59999999999988,
+ "504": 91.39999999999999,
+ "505": 109.95000000000013,
+ "506": 80.85000000000001,
+ "507": 107.70000000000013,
+ "508": 48.49999999999986,
+ "509": 89.94999999999992,
+ "510": 111.90000000000002,
+ "511": 117.90000000000028,
+ "512": 81.15,
+ "513": 110.90000000000008,
+ "514": 100.7500000000001,
+ "515": 76.34999999999978,
+ "516": 98.90000000000012,
+ "517": 10.450000000000063,
+ "518": 111.95000000000026,
+ "519": 107.90000000000015,
+ "520": 77.09999999999992,
+ "521": 114.10000000000024,
+ "522": 96.74999999999976,
+ "523": 104.15000000000005,
+ "524": 116.00000000000024,
+ "525": 105.80000000000018,
+ "526": 104.25000000000014,
+ "527": 101.89999999999986,
+ "528": 109.59999999999992,
+ "529": -0.9499999999999762,
+ "530": 112.50000000000007,
+ "531": 114.35000000000022,
+ "532": 99.80000000000005,
+ "533": 109.94999999999999,
+ "534": 112.8,
+ "535": 92.85000000000001,
+ "536": 104.35000000000007,
+ "537": 71.74999999999996,
+ "538": 99.50000000000004,
+ "539": 89.14999999999999,
+ "540": 56.599999999999916,
+ "541": 93.35000000000007,
+ "542": 80.69999999999996,
+ "543": 83.35000000000004,
+ "544": 88.54999999999997,
+ "545": 114.55000000000015,
+ "546": 107.25000000000006,
+ "547": 96.15000000000002,
+ "548": 109.6500000000001,
+ "549": 111.30000000000005,
+ "550": 101.55000000000011,
+ "551": 82.84999999999991,
+ "552": 113.60000000000005,
+ "553": 111.80000000000011,
+ "554": 111.40000000000006,
+ "555": 112.75000000000011,
+ "556": -42.10000000000001,
+ "557": 105.79999999999998,
+ "558": 111.05000000000001,
+ "559": 91.45000000000003,
+ "560": 96.75000000000004,
+ "561": 102.40000000000012,
+ "562": 109.15000000000003,
+ "563": 112.3000000000001,
+ "564": 101.55000000000018,
+ "565": 78.85000000000002,
+ "566": -21.000000000000014,
+ "567": 104.10000000000012,
+ "568": 34.09999999999992,
+ "569": 37.29999999999989,
+ "570": 96.64999999999988,
+ "571": 98.00000000000006,
+ "572": 100.35000000000021,
+ "573": 114.00000000000016,
+ "574": 112.40000000000016,
+ "575": -9.799999999999962,
+ "576": 102.9500000000001,
+ "577": 115.20000000000017,
+ "578": 93.8499999999999,
+ "579": 115.00000000000018,
+ "580": 89.25000000000009,
+ "581": 107.00000000000023,
+ "582": 112.35000000000021,
+ "583": 99.80000000000008,
+ "584": 107.65000000000013,
+ "585": 105.99999999999993,
+ "586": 109.80000000000025,
+ "587": 93.54999999999987,
+ "588": 90.65,
+ "589": 112.1500000000002,
+ "590": 112.15000000000013,
+ "591": 74.75000000000001,
+ "592": 112.05000000000021,
+ "593": 96.6500000000001,
+ "594": 99.7500000000001,
+ "595": 105.30000000000001,
+ "596": 85.09999999999982,
+ "597": 100.20000000000016,
+ "598": 109.95,
+ "599": 106.20000000000014,
+ "600": 111.20000000000014,
+ "601": 104.65000000000009,
+ "602": 104.45000000000026,
+ "603": 117.05000000000027,
+ "604": 109.60000000000022,
+ "605": 111.25,
+ "606": 44.64999999999984,
+ "607": 32.549999999999855,
+ "608": -12.049999999999978,
+ "609": 112.50000000000017,
+ "610": 87.30000000000004,
+ "611": 113.40000000000018,
+ "612": 94.74999999999982,
+ "613": 69.49999999999996,
+ "614": 109.35000000000011,
+ "615": 107.80000000000015,
+ "616": 103.49999999999993,
+ "617": 106.35000000000007,
+ "618": 34.099999999999724,
+ "619": 115.80000000000024,
+ "620": 107.60000000000007,
+ "621": 82.24999999999997,
+ "622": 90.35000000000001,
+ "623": 103.35000000000007,
+ "624": 112.70000000000019,
+ "625": 115.7500000000002,
+ "626": 107.45000000000007,
+ "627": 97.89999999999995,
+ "628": 85.04999999999983,
+ "629": 105.85000000000012,
+ "630": 106.00000000000011,
+ "631": 110.95000000000012,
+ "632": 87.04999999999977,
+ "633": 114.95000000000022,
+ "634": 110.1,
+ "635": 112.15000000000006,
+ "636": 87.6499999999998,
+ "637": 106.5000000000002,
+ "638": 111.65000000000003,
+ "639": 108.79999999999995,
+ "640": 108.2000000000002,
+ "641": 37.84999999999995,
+ "642": 83.15000000000002,
+ "643": 113.90000000000008,
+ "644": 49.2499999999998,
+ "645": 105.65000000000019,
+ "646": 107.30000000000018,
+ "647": 108.05000000000021,
+ "648": 100.40000000000015,
+ "649": 115.85000000000025,
+ "650": 100.99999999999993,
+ "651": 103.75000000000017,
+ "652": 113.60000000000024,
+ "653": 109.44999999999999,
+ "654": 109.34999999999987,
+ "655": 84.35000000000002,
+ "656": 77.99999999999979,
+ "657": 115.05000000000022,
+ "658": 109.65000000000003,
+ "659": 96.79999999999993,
+ "660": 109.44999999999999,
+ "661": 109.35000000000002,
+ "662": 103.40000000000013,
+ "663": 110.75000000000018,
+ "664": 116.3000000000002,
+ "665": 111.85000000000008,
+ "666": 111.55000000000021,
+ "667": 41.94999999999992,
+ "668": 95.04999999999994,
+ "669": 102.0500000000001,
+ "670": 111.40000000000018,
+ "671": 110.95000000000023,
+ "672": 91.79999999999997,
+ "673": 97.70000000000009,
+ "674": 107.25000000000003,
+ "675": 104.05000000000003,
+ "676": 55.099999999999916,
+ "677": 104.04999999999997,
+ "678": 113.30000000000021,
+ "679": 82.25000000000006,
+ "680": 110.55000000000003,
+ "681": 99.69999999999985,
+ "682": 108.55000000000001,
+ "683": 95.34999999999992,
+ "684": 95.20000000000012,
+ "685": 119.25000000000033,
+ "686": 102.04999999999991,
+ "687": 97.04999999999981,
+ "688": 102.25000000000009,
+ "689": 111.25000000000006,
+ "690": 110.94999999999997,
+ "691": 111.45000000000009,
+ "692": 107.45000000000006,
+ "693": 98.44999999999996,
+ "694": 105.3499999999998,
+ "695": 113.90000000000023,
+ "696": 104.60000000000014,
+ "697": 104.7000000000001,
+ "698": 98.65000000000012,
+ "699": 113.8500000000002,
+ "700": 104.99999999999999,
+ "701": 111.50000000000024,
+ "702": 109.5499999999999,
+ "703": 106.45000000000012,
+ "704": 102.19999999999995,
+ "705": 91.89999999999998,
+ "706": 114.20000000000019,
+ "707": 110.25000000000011,
+ "708": 106.75000000000018,
+ "709": 110.44999999999986,
+ "710": 112.95000000000013,
+ "711": 110.20000000000023,
+ "712": 104.75000000000007,
+ "713": 102.69999999999995,
+ "714": 110.4500000000002,
+ "715": 111.20000000000023,
+ "716": 101.39999999999995,
+ "717": 114.05000000000011,
+ "718": 102.75000000000014,
+ "719": 104.40000000000005,
+ "720": 112.4,
+ "721": 102.15000000000015,
+ "722": 113.9500000000002,
+ "723": 106.69999999999999,
+ "724": 112.19999999999997,
+ "725": 106.75000000000013,
+ "726": 104.85000000000001,
+ "727": 110.10000000000008,
+ "728": 105.09999999999997,
+ "729": 112.65000000000023,
+ "730": 84.89999999999995,
+ "731": 115.05000000000025,
+ "732": 103.34999999999994,
+ "733": 110.10000000000004,
+ "734": 117.25000000000001,
+ "735": 5.9499999999999975,
+ "736": 102.8499999999999,
+ "737": 111.30000000000014,
+ "738": 104.19999999999999,
+ "739": 111.44999999999997,
+ "740": 110.25000000000018,
+ "741": 83.14999999999995,
+ "742": 97.00000000000001,
+ "743": 113.05000000000022,
+ "744": 98.85000000000015,
+ "745": 101.19999999999997,
+ "746": 94.45000000000006,
+ "747": 112.15000000000013,
+ "748": 113.95000000000003,
+ "749": 100.80000000000003,
+ "750": 100.70000000000017,
+ "751": 109.60000000000016,
+ "752": 87.54999999999987,
+ "753": 108.7999999999998,
+ "754": 94.24999999999996,
+ "755": 53.99999999999991,
+ "756": 103.34999999999998,
+ "757": 102.50000000000006,
+ "758": 108.85000000000008,
+ "759": 111.75000000000014,
+ "760": 107.80000000000022,
+ "761": 102.65000000000013,
+ "762": 103.69999999999999,
+ "763": 100.35000000000011,
+ "764": 101.3,
+ "765": 113.64999999999995,
+ "766": 112.75000000000014,
+ "767": 97.59999999999998,
+ "768": 110.04999999999988,
+ "769": 114.15000000000019,
+ "770": 106.15000000000003,
+ "771": 104.94999999999992,
+ "772": 104.99999999999994,
+ "773": 102.60000000000012,
+ "774": 104.80000000000001,
+ "775": 101.75000000000006,
+ "776": 112.50000000000014,
+ "777": 111.35000000000007,
+ "778": 117.40000000000025,
+ "779": 110.3500000000002,
+ "780": 104.35000000000012,
+ "781": 25.199999999999942,
+ "782": 72.69999999999996,
+ "783": 108.70000000000005,
+ "784": 113.25000000000009,
+ "785": 114.6499999999999,
+ "786": 98.85000000000012,
+ "787": 112.85000000000021,
+ "788": 110.1000000000002,
+ "789": 101.15000000000008,
+ "790": 111.30000000000017,
+ "791": 108.85000000000016,
+ "792": 42.39999999999982,
+ "793": 103.79999999999981,
+ "794": 108.9500000000002,
+ "795": 108.74999999999997,
+ "796": 112.19999999999993,
+ "797": 105.59999999999994,
+ "798": 108.40000000000018,
+ "799": 98.79999999999998,
+ "800": 103.85000000000008,
+ "801": 116.45000000000014,
+ "802": 104.40000000000002,
+ "803": 109.19999999999993,
+ "804": 107.1500000000001,
+ "805": 112.50000000000013,
+ "806": 112.35000000000021,
+ "807": 115.4000000000002,
+ "808": 104.80000000000001,
+ "809": 108.45000000000016,
+ "810": 111.40000000000003,
+ "811": 116.45000000000005,
+ "812": 116.70000000000002,
+ "813": 110.85000000000014,
+ "814": 108.15000000000009,
+ "815": 105.95000000000019,
+ "816": 107.15000000000005,
+ "817": 107.20000000000012,
+ "818": 111.54999999999986,
+ "819": 113.10000000000021,
+ "820": 105.89999999999989,
+ "821": 109.10000000000012,
+ "822": 108.40000000000002,
+ "823": 93.94999999999983,
+ "824": 107.60000000000011,
+ "825": 113.99999999999999,
+ "826": 108.39999999999975,
+ "827": 114.94999999999987,
+ "828": 111.05000000000003,
+ "829": 109.14999999999989,
+ "830": 107.14999999999984,
+ "831": 28.6,
+ "832": 115.10000000000012,
+ "833": 117.3000000000001,
+ "834": 111.90000000000008,
+ "835": 105.15000000000002,
+ "836": 105.84999999999977,
+ "837": 45.299999999999926,
+ "838": 49.29999999999984,
+ "839": 103.35000000000012,
+ "840": 110.95000000000014,
+ "841": 52.54999999999985,
+ "842": 114.25000000000014,
+ "843": 101.59999999999995,
+ "844": 98.40000000000005,
+ "845": 111.40000000000008,
+ "846": 84.44999999999995,
+ "847": 98.99999999999996,
+ "848": 82.69999999999983,
+ "849": 105.80000000000008,
+ "850": 111.15000000000012,
+ "851": -46.64999999999997,
+ "852": 105.30000000000011,
+ "853": 109.90000000000012,
+ "854": -15.19999999999998,
+ "855": 107.40000000000002,
+ "856": 106.34999999999992,
+ "857": 117.65000000000015,
+ "858": 111.95000000000016,
+ "859": 110.64999999999992,
+ "860": 109.5500000000001,
+ "861": 19.19999999999997,
+ "862": 113.85000000000022,
+ "863": 113.00000000000018,
+ "864": 111.60000000000008,
+ "865": 106.95000000000013,
+ "866": -30.799999999999965,
+ "867": 106.45000000000017,
+ "868": 113.50000000000017,
+ "869": 50.6499999999998,
+ "870": -0.8499999999999961,
+ "871": 103.00000000000011,
+ "872": -36.55,
+ "873": 101.10000000000015,
+ "874": 107.59999999999991,
+ "875": 113.60000000000001,
+ "876": 114.50000000000013,
+ "877": 114.6500000000001,
+ "878": 112.30000000000007,
+ "879": 109.40000000000003,
+ "880": 96.00000000000001,
+ "881": 104.60000000000008,
+ "882": 111.00000000000006,
+ "883": 17.45000000000004,
+ "884": 45.89999999999978,
+ "885": 109.64999999999998,
+ "886": 101.25000000000007,
+ "887": 112.40000000000003,
+ "888": 78.24999999999983,
+ "889": 116.45000000000007,
+ "890": 106.75000000000016,
+ "891": 115.99999999999997,
+ "892": 7.550000000000008,
+ "893": 117.65000000000009,
+ "894": 81.99999999999993,
+ "895": 75.59999999999991,
+ "896": 112.75000000000003,
+ "897": 110.75,
+ "898": 111.10000000000011,
+ "899": 72.79999999999995,
+ "900": 111.1500000000001,
+ "901": 111.35000000000007,
+ "902": 113.10000000000026,
+ "903": -66.05,
+ "904": 112.50000000000004,
+ "905": 22.449999999999935,
+ "906": 113.00000000000013,
+ "907": 110.39999999999999,
+ "908": 115.55000000000008,
+ "909": 106.95000000000006,
+ "910": 77.25000000000001,
+ "911": 107.20000000000005,
+ "912": 105.35000000000016,
+ "913": 114.6000000000001,
+ "914": 110.55000000000005,
+ "915": 118.55000000000011,
+ "916": 109.60000000000012,
+ "917": 59.09999999999994,
+ "918": 102.2500000000001,
+ "919": 112.50000000000009,
+ "920": 106.10000000000012,
+ "921": 112.60000000000008,
+ "922": 113.70000000000016,
+ "923": 114.94999999999995,
+ "924": 118.50000000000013,
+ "925": 106.05000000000003,
+ "926": 109.65000000000006,
+ "927": 118.10000000000015,
+ "928": 117.00000000000006,
+ "929": 43.59999999999991,
+ "930": 110.25000000000009,
+ "931": 102.20000000000007,
+ "932": 106.9000000000001,
+ "933": 107.00000000000003,
+ "934": 111.45000000000009,
+ "935": 114.45000000000016,
+ "936": 111.2500000000002,
+ "937": 116.09999999999998,
+ "938": 116.70000000000009,
+ "939": 118.00000000000017,
+ "940": 106.09999999999991,
+ "941": 105.75000000000006,
+ "942": 109.94999999999996,
+ "943": 109.50000000000011,
+ "944": 115.60000000000001,
+ "945": 107.40000000000006,
+ "946": 107.55000000000005,
+ "947": 111.55000000000018,
+ "948": 111.70000000000005,
+ "949": 109.4499999999999,
+ "950": 113.45000000000012,
+ "951": 109.04999999999998,
+ "952": 114.80000000000001,
+ "953": 119.40000000000018,
+ "954": 118.60000000000002,
+ "955": 100.44999999999986,
+ "956": 103.45000000000003,
+ "957": 116.65000000000018,
+ "958": 106.75000000000009,
+ "959": 115.95000000000016,
+ "960": 107.15000000000006,
+ "961": 113.20000000000002,
+ "962": 108.84999999999992,
+ "963": 106.3000000000002,
+ "964": 98.14999999999976,
+ "965": 115.50000000000009,
+ "966": 114.65000000000005,
+ "967": 118.20000000000013,
+ "968": 107.40000000000003,
+ "969": 107.80000000000005,
+ "970": 106.85000000000018,
+ "971": 109.34999999999988,
+ "972": 109.85000000000015,
+ "973": 106.9,
+ "974": 113.79999999999997,
+ "975": 108.70000000000012,
+ "976": 112.70000000000007,
+ "977": 109.65000000000008,
+ "978": 104.20000000000002,
+ "979": 110.2000000000001,
+ "980": 108.00000000000009,
+ "981": -27.549999999999994,
+ "982": 120.30000000000015,
+ "983": 110.15000000000006,
+ "984": 111.10000000000024,
+ "985": 114.20000000000009,
+ "986": 112.90000000000005,
+ "987": 114.95,
+ "988": 108.80000000000001,
+ "989": 111.30000000000014,
+ "990": 112.15000000000008,
+ "991": 112.04999999999995,
+ "992": 104.54999999999995,
+ "993": 109.10000000000001,
+ "994": 112.60000000000018,
+ "995": 115.20000000000006,
+ "996": 115.10000000000018,
+ "997": 107.29999999999994,
+ "998": 110.30000000000003,
+ "999": 113.84999999999998,
+ "1000": 112.2500000000002
+ },
+ "3": {
+ "1": -83.14999999999995,
+ "2": -3.3999999999999924,
+ "3": -34.44999999999999,
+ "4": -26.64999999999995,
+ "5": -66.90000000000003,
+ "6": -16.399999999999974,
+ "7": -60.50000000000016,
+ "8": -19.899999999999963,
+ "9": -34.49999999999998,
+ "10": -38.600000000000115,
+ "11": -55.10000000000009,
+ "12": -16.249999999999975,
+ "13": -18.049999999999972,
+ "14": -7.449999999999982,
+ "15": -95.25,
+ "16": -5.099999999999988,
+ "17": -60.5000000000001,
+ "18": -19.349999999999955,
+ "19": -52.600000000000186,
+ "20": -12.150000000000006,
+ "21": -9.599999999999973,
+ "22": -18.04999999999997,
+ "23": -7.04999999999998,
+ "24": -20.29999999999996,
+ "25": -15.249999999999977,
+ "26": -18.80000000000001,
+ "27": -8.499999999999991,
+ "28": -97.45,
+ "29": -12.449999999999987,
+ "30": -64.10000000000011,
+ "31": -15.599999999999982,
+ "32": -18.749999999999968,
+ "33": -51.25000000000007,
+ "34": 6.60000000000002,
+ "35": -8.999999999999998,
+ "36": -15.799999999999976,
+ "37": -9.099999999999996,
+ "38": 0.9000000000000177,
+ "39": -18.99999999999999,
+ "40": -13.949999999999985,
+ "41": -10.249999999999982,
+ "42": -97.40000000000005,
+ "43": -9.049999999999988,
+ "44": -9.749999999999996,
+ "45": -95.95,
+ "46": 13.150000000000047,
+ "47": -80.19999999999996,
+ "48": -14.149999999999988,
+ "49": -88.85000000000001,
+ "50": -21.050000000000008,
+ "51": -15.599999999999978,
+ "52": -19.999999999999964,
+ "53": -18.69999999999997,
+ "54": -75.99999999999997,
+ "55": -23.999999999999943,
+ "56": -19.649999999999956,
+ "57": -10.449999999999996,
+ "58": -71.3,
+ "59": -3.9499999999999984,
+ "60": -68.70000000000005,
+ "61": -95.6,
+ "62": -14.59999999999998,
+ "63": -15.449999999999978,
+ "64": -29.050000000000004,
+ "65": -12.199999999999998,
+ "66": -8.649999999999993,
+ "67": -93.75,
+ "68": -74.74999999999991,
+ "69": -77.1,
+ "70": -84.19999999999983,
+ "71": -22.649999999999952,
+ "72": 1.8000000000000231,
+ "73": -59.54999999999997,
+ "74": 21.549999999999997,
+ "75": -16.749999999999975,
+ "76": -1.449999999999974,
+ "77": -67.95,
+ "78": -15.799999999999974,
+ "79": -17.499999999999975,
+ "80": -42.750000000000156,
+ "81": -20.49999999999996,
+ "82": -93.75000000000014,
+ "83": -32.40000000000004,
+ "84": -15.549999999999976,
+ "85": -15.799999999999976,
+ "86": -59.3000000000001,
+ "87": -28.5,
+ "88": -55.70000000000008,
+ "89": -7.899999999999994,
+ "90": 6.000000000000044,
+ "91": -34.65000000000005,
+ "92": -52.800000000000004,
+ "93": -33.50000000000003,
+ "94": -103.45,
+ "95": -99.7,
+ "96": -58.85000000000001,
+ "97": -7.549999999999986,
+ "98": 2.7000000000000286,
+ "99": -12.599999999999987,
+ "100": -1.9499999999999718,
+ "101": -10.649999999999988,
+ "102": -93.9,
+ "103": -17.19999999999997,
+ "104": -7.049999999999956,
+ "105": -83.50000000000001,
+ "106": 3.4500000000000393,
+ "107": -14.999999999999977,
+ "108": -12.699999999999985,
+ "109": -62.20000000000006,
+ "110": -6.849999999999987,
+ "111": 8.150000000000059,
+ "112": -19.64999999999996,
+ "113": -24.399999999999945,
+ "114": -36.90000000000004,
+ "115": 30.099999999999923,
+ "116": -62.700000000000074,
+ "117": -11.649999999999993,
+ "118": -83.79999999999981,
+ "119": -11.099999999999993,
+ "120": 19.949999999999992,
+ "121": -60.150000000000006,
+ "122": -4.249999999999994,
+ "123": -8.999999999999991,
+ "124": -54.5000000000001,
+ "125": 2.250000000000018,
+ "126": -0.09999999999999276,
+ "127": 9.950000000000003,
+ "128": -44.050000000000004,
+ "129": -47.250000000000064,
+ "130": -32.20000000000005,
+ "131": -22.499999999999954,
+ "132": -5.799999999999981,
+ "133": 23.549999999999915,
+ "134": -10.24999999999999,
+ "135": 10.050000000000022,
+ "136": -11.849999999999985,
+ "137": -88.5,
+ "138": 5.250000000000002,
+ "139": 26.299999999999997,
+ "140": -60.250000000000014,
+ "141": -4.799999999999986,
+ "142": -33.09999999999999,
+ "143": -18.89999999999997,
+ "144": -20.24999999999996,
+ "145": -46.34999999999994,
+ "146": -2.9499999999999957,
+ "147": -2.599999999999973,
+ "148": -48.25000000000003,
+ "149": 0.8000000000000278,
+ "150": -5.949999999999991,
+ "151": -85.05,
+ "152": -37.30000000000009,
+ "153": -18.44999999999997,
+ "154": -46.799999999999976,
+ "155": -15.849999999999977,
+ "156": -54.34999999999997,
+ "157": -61.70000000000005,
+ "158": 9.850000000000005,
+ "159": -81.8,
+ "160": -93.94999999999999,
+ "161": -5.099999999999979,
+ "162": 13.200000000000028,
+ "163": -85.59999999999997,
+ "164": -74.55000000000001,
+ "165": -89.75,
+ "166": -15.049999999999969,
+ "167": -36.55000000000003,
+ "168": -21.399999999999956,
+ "169": -32.1,
+ "170": -58.84999999999996,
+ "171": -58.050000000000026,
+ "172": -24.999999999999993,
+ "173": 18.150000000000055,
+ "174": 15.949999999999946,
+ "175": 13.350000000000012,
+ "176": -1.749999999999979,
+ "177": -18.400000000000002,
+ "178": -80.9,
+ "179": -76.70000000000002,
+ "180": -4.399999999999975,
+ "181": 12.250000000000064,
+ "182": -19.749999999999993,
+ "183": -1.6499999999999775,
+ "184": -17.849999999999977,
+ "185": 13.050000000000011,
+ "186": 16.49999999999998,
+ "187": 22.300000000000058,
+ "188": -59.6500000000001,
+ "189": -12.49999999999999,
+ "190": -31.749999999999993,
+ "191": -21.650000000000002,
+ "192": -24.999999999999986,
+ "193": -17.749999999999975,
+ "194": -34.00000000000001,
+ "195": -25.35,
+ "196": 15.000000000000053,
+ "197": 4.700000000000045,
+ "198": -6.1,
+ "199": -55.849999999999945,
+ "200": 10.899999999999977,
+ "201": 9.150000000000038,
+ "202": 11.450000000000019,
+ "203": -15.649999999999977,
+ "204": -19.349999999999962,
+ "205": -59.85,
+ "206": 25.1999999999999,
+ "207": -14.299999999999981,
+ "208": 47.24999999999978,
+ "209": -25.200000000000014,
+ "210": -36.60000000000001,
+ "211": -5.199999999999979,
+ "212": -22.249999999999975,
+ "213": -30.999999999999968,
+ "214": -45.35000000000015,
+ "215": -70.10000000000004,
+ "216": -49.10000000000001,
+ "217": -0.6500000000000115,
+ "218": -50.85000000000001,
+ "219": -14.54999999999998,
+ "220": -52.50000000000008,
+ "221": 32.44999999999994,
+ "222": 15.199999999999978,
+ "223": -10.749999999999995,
+ "224": -63.54999999999999,
+ "225": -91.54999999999995,
+ "226": 63.19999999999992,
+ "227": -14.49999999999998,
+ "228": -12.349999999999985,
+ "229": -61.750000000000036,
+ "230": -14.849999999999978,
+ "231": -3.7499999999999902,
+ "232": 4.600000000000036,
+ "233": -29.349999999999987,
+ "234": -57.2,
+ "235": 14.3,
+ "236": 17.34999999999998,
+ "237": 35.149999999999906,
+ "238": 16.699999999999914,
+ "239": -3.599999999999987,
+ "240": 4.1300296516055823e-14,
+ "241": -34.04999999999996,
+ "242": -20.249999999999957,
+ "243": -31.85000000000009,
+ "244": 34.099999999999845,
+ "245": 56.69999999999988,
+ "246": -77.19999999999985,
+ "247": -72.30000000000005,
+ "248": 59.09999999999989,
+ "249": -4.94999999999999,
+ "250": 27.150000000000013,
+ "251": -8.950000000000017,
+ "252": 29.700000000000024,
+ "253": -66.55000000000001,
+ "254": 52.29999999999996,
+ "255": -5.699999999999981,
+ "256": 43.299999999999805,
+ "257": -8.800000000000006,
+ "258": 45.2499999999999,
+ "259": -20.94999999999996,
+ "260": 10.350000000000037,
+ "261": 22.299999999999862,
+ "262": 77.94999999999983,
+ "263": 25.249999999999957,
+ "264": 23.4,
+ "265": 80.74999999999976,
+ "266": -61.95000000000001,
+ "267": -3.949999999999977,
+ "268": 91.54999999999978,
+ "269": 36.79999999999994,
+ "270": 10.750000000000009,
+ "271": 59.54999999999984,
+ "272": 30.349999999999966,
+ "273": 40.34999999999995,
+ "274": 22.55000000000001,
+ "275": -39.50000000000006,
+ "276": 11.89999999999996,
+ "277": 79.40000000000005,
+ "278": 53.1499999999999,
+ "279": 21.549999999999997,
+ "280": -12.649999999999988,
+ "281": 5.800000000000001,
+ "282": -9.449999999999992,
+ "283": 45.69999999999987,
+ "284": -6.300000000000009,
+ "285": -55.24999999999998,
+ "286": 61.69999999999993,
+ "287": -13.79999999999997,
+ "288": -5.500000000000008,
+ "289": 23.24999999999997,
+ "290": 89.64999999999992,
+ "291": 43.2999999999999,
+ "292": 27.449999999999907,
+ "293": 87.34999999999985,
+ "294": 70.04999999999993,
+ "295": 28.39999999999996,
+ "296": -46.55,
+ "297": -1.6500000000000368,
+ "298": 56.44999999999974,
+ "299": 52.84999999999982,
+ "300": 31.60000000000004,
+ "301": 9.550000000000015,
+ "302": 4.60000000000001,
+ "303": 44.89999999999985,
+ "304": 18.949999999999974,
+ "305": 50.099999999999895,
+ "306": -3.100000000000022,
+ "307": 58.34999999999985,
+ "308": 72.19999999999996,
+ "309": 61.29999999999985,
+ "310": 13.600000000000051,
+ "311": 37.999999999999844,
+ "312": 37.74999999999993,
+ "313": 15.249999999999968,
+ "314": 7.050000000000011,
+ "315": 27.149999999999917,
+ "316": 87.65,
+ "317": 41.89999999999994,
+ "318": -13.850000000000005,
+ "319": 54.899999999999736,
+ "320": 98.04999999999994,
+ "321": 85.84999999999978,
+ "322": 67.15000000000002,
+ "323": 12.449999999999996,
+ "324": 33.99999999999978,
+ "325": 103.35000000000016,
+ "326": 54.499999999999964,
+ "327": 69.14999999999984,
+ "328": 110.2000000000002,
+ "329": 98.00000000000017,
+ "330": 108.35000000000022,
+ "331": 63.84999999999994,
+ "332": 47.599999999999746,
+ "333": 62.24999999999994,
+ "334": 77.54999999999995,
+ "335": 29.299999999999805,
+ "336": 84.90000000000005,
+ "337": 112.70000000000024,
+ "338": 91.9500000000001,
+ "339": 14.69999999999994,
+ "340": 96.79999999999991,
+ "341": 42.74999999999992,
+ "342": 44.74999999999992,
+ "343": 80.95000000000012,
+ "344": 48.399999999999885,
+ "345": -14.699999999999976,
+ "346": 85.69999999999985,
+ "347": 48.74999999999993,
+ "348": 53.599999999999845,
+ "349": 8.65000000000003,
+ "350": 80.80000000000004,
+ "351": 117.40000000000022,
+ "352": 81.54999999999991,
+ "353": 87.55000000000007,
+ "354": 111.30000000000022,
+ "355": 79.89999999999993,
+ "356": 89.10000000000005,
+ "357": 83.04999999999994,
+ "358": 78.29999999999997,
+ "359": 65.14999999999985,
+ "360": 85.79999999999994,
+ "361": 86.75000000000006,
+ "362": -68.15,
+ "363": 60.899999999999956,
+ "364": 103.4499999999998,
+ "365": 104.10000000000004,
+ "366": 62.24999999999981,
+ "367": 78.74999999999979,
+ "368": 112.00000000000013,
+ "369": 100.89999999999989,
+ "370": 107.05000000000015,
+ "371": 93.95000000000006,
+ "372": 42.049999999999784,
+ "373": 104.10000000000008,
+ "374": 109.25000000000021,
+ "375": 76.1999999999998,
+ "376": 25.449999999999896,
+ "377": -17.049999999999972,
+ "378": 105.40000000000012,
+ "379": 112.55000000000011,
+ "380": 106.75000000000007,
+ "381": 20.799999999999972,
+ "382": 102.90000000000002,
+ "383": 88.40000000000008,
+ "384": -50.999999999999936,
+ "385": 86.5500000000002,
+ "386": 97.15000000000013,
+ "387": 104.50000000000016,
+ "388": 101.80000000000013,
+ "389": 106.7500000000002,
+ "390": 111.95000000000026,
+ "391": 78.80000000000005,
+ "392": 102.4,
+ "393": 76.95000000000007,
+ "394": 114.00000000000018,
+ "395": 102.50000000000026,
+ "396": 91.90000000000012,
+ "397": 87.30000000000007,
+ "398": 111.35000000000022,
+ "399": 113.95000000000014,
+ "400": 13.300000000000024,
+ "401": 84.39999999999993,
+ "402": 79.29999999999998,
+ "403": 108.40000000000016,
+ "404": 45.99999999999993,
+ "405": 90.50000000000004,
+ "406": 55.79999999999986,
+ "407": 111.65000000000022,
+ "408": 106.54999999999998,
+ "409": 98.65000000000009,
+ "410": 86.39999999999996,
+ "411": 97.45000000000009,
+ "412": 103.95000000000012,
+ "413": 86.5500000000001,
+ "414": 95.90000000000013,
+ "415": 97.40000000000012,
+ "416": 102.60000000000002,
+ "417": 95.70000000000013,
+ "418": 97.85000000000011,
+ "419": 101.94999999999979,
+ "420": 86.49999999999979,
+ "421": 101.8500000000001,
+ "422": 115.10000000000002,
+ "423": 111.80000000000014,
+ "424": 112.35000000000028,
+ "425": 101.85000000000004,
+ "426": 103.00000000000016,
+ "427": 111.90000000000005,
+ "428": 109.39999999999986,
+ "429": 76.80000000000007,
+ "430": 108.60000000000018,
+ "431": 113.15000000000016,
+ "432": 111.95000000000005,
+ "433": 102.65000000000019,
+ "434": 81.15,
+ "435": 103.85000000000015,
+ "436": 110.35000000000012,
+ "437": 90.4499999999999,
+ "438": 104.94999999999992,
+ "439": 56.69999999999987,
+ "440": 104.20000000000017,
+ "441": 91.30000000000011,
+ "442": 109.24999999999999,
+ "443": 82.09999999999988,
+ "444": 99.54999999999994,
+ "445": 116.40000000000008,
+ "446": 107.64999999999988,
+ "447": 117.45000000000013,
+ "448": 117.75000000000028,
+ "449": 112.10000000000002,
+ "450": 106.45000000000017,
+ "451": 32.34999999999981,
+ "452": 98.95000000000002,
+ "453": 81.25000000000007,
+ "454": 114.3500000000001,
+ "455": 119.70000000000022,
+ "456": 113.15000000000023,
+ "457": 109.20000000000026,
+ "458": 113.85000000000014,
+ "459": 54.29999999999982,
+ "460": 4.8500000000000005,
+ "461": 98.20000000000014,
+ "462": 59.599999999999866,
+ "463": 100.20000000000007,
+ "464": 90.85000000000012,
+ "465": 104.10000000000008,
+ "466": 113.00000000000009,
+ "467": 115.50000000000026,
+ "468": 112.90000000000016,
+ "469": 110.84999999999997,
+ "470": 116.85,
+ "471": 96.85000000000004,
+ "472": 113.45000000000005,
+ "473": 91.29999999999995,
+ "474": 112.85000000000004,
+ "475": 68.79999999999991,
+ "476": 109.35000000000015,
+ "477": 117.65000000000019,
+ "478": 83.75000000000024,
+ "479": 116.20000000000022,
+ "480": 113.70000000000003,
+ "481": 112.95000000000007,
+ "482": 109.85000000000011,
+ "483": 110.7000000000002,
+ "484": 115.40000000000005,
+ "485": 111.94999999999997,
+ "486": 103.55000000000008,
+ "487": 91.14999999999985,
+ "488": 89.55,
+ "489": 88.65000000000019,
+ "490": 120.95000000000007,
+ "491": 112.60000000000002,
+ "492": 96.94999999999996,
+ "493": 111.99999999999993,
+ "494": 94.00000000000003,
+ "495": 116.20000000000002,
+ "496": 104.65000000000013,
+ "497": 107.65000000000009,
+ "498": 102.70000000000023,
+ "499": 34.69999999999977,
+ "500": 91.20000000000017,
+ "501": 118.65000000000003,
+ "502": 112.10000000000018,
+ "503": 113.75000000000009,
+ "504": 34.60000000000001,
+ "505": 105.40000000000005,
+ "506": 101.85000000000014,
+ "507": 110.09999999999997,
+ "508": 116.40000000000022,
+ "509": 107.1000000000001,
+ "510": 117.60000000000008,
+ "511": -4.399999999999987,
+ "512": 108.95000000000013,
+ "513": 106.70000000000003,
+ "514": 103.55000000000013,
+ "515": 93.30000000000003,
+ "516": 114.00000000000026,
+ "517": 112.4000000000001,
+ "518": 97.35000000000004,
+ "519": 103.90000000000002,
+ "520": 110.30000000000015,
+ "521": 105.30000000000014,
+ "522": 109.7500000000001,
+ "523": 122.10000000000004,
+ "524": 113.80000000000011,
+ "525": 81.55000000000007,
+ "526": 113.85000000000007,
+ "527": 115.85000000000004,
+ "528": 112.30000000000005,
+ "529": 114.45000000000013,
+ "530": 107.19999999999999,
+ "531": 105.60000000000004,
+ "532": 109.10000000000018,
+ "533": -12.499999999999986,
+ "534": 106.44999999999999,
+ "535": 106.65000000000005,
+ "536": 110.0000000000001,
+ "537": 106.95000000000013,
+ "538": 95.20000000000007,
+ "539": 117.00000000000007,
+ "540": 114.75000000000017,
+ "541": 121.6000000000001,
+ "542": 121.65000000000019,
+ "543": 77.39999999999998,
+ "544": 112.04999999999998,
+ "545": 104.75000000000003,
+ "546": 110.1500000000002,
+ "547": 116.34999999999997,
+ "548": 110.30000000000017,
+ "549": 107.60000000000012,
+ "550": 116.00000000000007,
+ "551": 99.30000000000022,
+ "552": 114.60000000000007,
+ "553": 106.85000000000007,
+ "554": 114.1500000000002,
+ "555": 111.3500000000001,
+ "556": 118.45000000000022,
+ "557": 105.55000000000008,
+ "558": 108.40000000000018,
+ "559": 112.80000000000008,
+ "560": 111.35000000000012,
+ "561": 109.65000000000008,
+ "562": 116.00000000000004,
+ "563": 109.80000000000021,
+ "564": 102.1,
+ "565": 102.64999999999998,
+ "566": 108.35000000000007,
+ "567": 115.5000000000002,
+ "568": 113.55000000000018,
+ "569": 113.65000000000005,
+ "570": 118.9500000000002,
+ "571": 105.35,
+ "572": 109.50000000000007,
+ "573": 106.85000000000001,
+ "574": 93.75000000000009,
+ "575": 114.24999999999997,
+ "576": 107.79999999999995,
+ "577": 119.04999999999998,
+ "578": 104.6500000000001,
+ "579": 114.25000000000023,
+ "580": 99.6500000000001,
+ "581": 117.65,
+ "582": 112.10000000000014,
+ "583": 91.50000000000004,
+ "584": 109.35000000000014,
+ "585": 117.40000000000005,
+ "586": 105.30000000000011,
+ "587": 107.85000000000007,
+ "588": 118.64999999999995,
+ "589": 117.75000000000011,
+ "590": 106.85000000000022,
+ "591": 71.74999999999999,
+ "592": 60.900000000000006,
+ "593": 89.20000000000017,
+ "594": 107.35000000000016,
+ "595": 116.80000000000008,
+ "596": 104.69999999999996,
+ "597": 110.60000000000001,
+ "598": 56.69999999999992,
+ "599": 14.55000000000003,
+ "600": 64.69999999999999,
+ "601": 90.10000000000012,
+ "602": 106.75000000000011,
+ "603": 93.95000000000019,
+ "604": 104.00000000000017,
+ "605": 80.29999999999998,
+ "606": 109.15000000000006,
+ "607": 99.80000000000015,
+ "608": 55.04999999999983,
+ "609": 82.15000000000003,
+ "610": 96.1,
+ "611": 53.30000000000002,
+ "612": 95.44999999999999,
+ "613": 85.6,
+ "614": 116.30000000000008,
+ "615": 111.90000000000008,
+ "616": 76.04999999999998,
+ "617": 104.30000000000005,
+ "618": 104.50000000000017,
+ "619": 119.70000000000005,
+ "620": 117.30000000000004,
+ "621": 107.60000000000004,
+ "622": 105.15000000000016,
+ "623": 101.30000000000008,
+ "624": 116.99999999999997,
+ "625": 24.299999999999983,
+ "626": 100.39999999999998,
+ "627": 102.99999999999996,
+ "628": 100.99999999999994,
+ "629": 106.5500000000002,
+ "630": 109.50000000000001,
+ "631": 119.65000000000006,
+ "632": 99.50000000000007,
+ "633": 98.54999999999995,
+ "634": 88.85000000000008,
+ "635": 119.3000000000001,
+ "636": 124.20000000000006,
+ "637": 109.85000000000005,
+ "638": 116.00000000000018,
+ "639": 104.15000000000015,
+ "640": 112.35000000000005,
+ "641": 65.44999999999995,
+ "642": 110.90000000000015,
+ "643": 109.30000000000013,
+ "644": 109.70000000000019,
+ "645": 98.55000000000005,
+ "646": 90.3500000000001,
+ "647": 115.3,
+ "648": 108.55000000000007,
+ "649": 112.55000000000011,
+ "650": 109.15000000000015,
+ "651": 115.25000000000018,
+ "652": 104.55000000000013,
+ "653": 114.6500000000001,
+ "654": 86.85000000000007,
+ "655": 112.05000000000005,
+ "656": 112.25000000000003,
+ "657": 105.85000000000002,
+ "658": 75.79999999999991,
+ "659": 116.6000000000002,
+ "660": 113.70000000000016,
+ "661": 121.60000000000011,
+ "662": 97.7500000000001,
+ "663": 101.05000000000003,
+ "664": 114.00000000000009,
+ "665": 111.95000000000003,
+ "666": 111.15000000000008,
+ "667": 113.10000000000008,
+ "668": 109.35000000000008,
+ "669": 90.65000000000003,
+ "670": 107.40000000000018,
+ "671": 110.75,
+ "672": 114.25,
+ "673": 112.7,
+ "674": 89.49999999999999,
+ "675": 105.75000000000018,
+ "676": 114.75000000000014,
+ "677": 112.75000000000013,
+ "678": 115.90000000000023,
+ "679": 117.8000000000002,
+ "680": 114.25000000000018,
+ "681": 68.3000000000001,
+ "682": 119.9500000000001,
+ "683": 105.95000000000009,
+ "684": 114.25000000000011,
+ "685": 100.30000000000011,
+ "686": 93.69999999999989,
+ "687": 105.00000000000001,
+ "688": 110.30000000000003,
+ "689": 118.15000000000015,
+ "690": 106.20000000000013,
+ "691": 93.59999999999988,
+ "692": 100.15000000000012,
+ "693": 117.05000000000001,
+ "694": 110.55000000000008,
+ "695": 113.20000000000013,
+ "696": 110.75000000000018,
+ "697": 114.90000000000018,
+ "698": 112.10000000000012,
+ "699": 108.70000000000019,
+ "700": 107.69999999999993,
+ "701": 120.45000000000003,
+ "702": 113.80000000000013,
+ "703": 101.8,
+ "704": 119.20000000000022,
+ "705": 114.3500000000001,
+ "706": 108.75,
+ "707": 113.04999999999993,
+ "708": 113.85000000000014,
+ "709": 111.4500000000001,
+ "710": 106.80000000000004,
+ "711": 104.94999999999986,
+ "712": 108.75000000000014,
+ "713": 101.8000000000002,
+ "714": 100.30000000000003,
+ "715": 92.74999999999984,
+ "716": 99.8000000000001,
+ "717": 110.10000000000012,
+ "718": 55.89999999999982,
+ "719": 115.75000000000001,
+ "720": 120.75000000000004,
+ "721": 116.90000000000006,
+ "722": 106.70000000000002,
+ "723": 109.70000000000007,
+ "724": 105.40000000000003,
+ "725": 117.14999999999998,
+ "726": 118.15000000000003,
+ "727": 119.25000000000013,
+ "728": 115.65000000000012,
+ "729": 110.95,
+ "730": 111.55000000000011,
+ "731": 111.15000000000013,
+ "732": 116.85000000000004,
+ "733": 117.15000000000005,
+ "734": 113.4000000000002,
+ "735": 107.84999999999998,
+ "736": 116.69999999999996,
+ "737": 115.05000000000007,
+ "738": 76.20000000000002,
+ "739": 108.85000000000005,
+ "740": 99.10000000000012,
+ "741": 116.95000000000005,
+ "742": 109.35000000000015,
+ "743": 110.70000000000016,
+ "744": 109.55000000000003,
+ "745": 116.75000000000013,
+ "746": 119.65000000000005,
+ "747": 109.8500000000001,
+ "748": 114.90000000000006,
+ "749": 112.00000000000021,
+ "750": 106.39999999999996,
+ "751": 91.60000000000005,
+ "752": 103.90000000000009,
+ "753": 111.1000000000002,
+ "754": 112.80000000000013,
+ "755": 102.45000000000009,
+ "756": 112.30000000000004,
+ "757": 110.0,
+ "758": 111.65000000000005,
+ "759": 117.45000000000017,
+ "760": 110.85000000000011,
+ "761": 110.15000000000009,
+ "762": 112.70000000000002,
+ "763": 110.34999999999988,
+ "764": 111.00000000000009,
+ "765": 96.19999999999999,
+ "766": 113.25000000000013,
+ "767": 106.79999999999988,
+ "768": 116.25000000000014,
+ "769": 114.10000000000022,
+ "770": 112.05000000000005,
+ "771": 123.35000000000004,
+ "772": 94.65000000000006,
+ "773": 111.35000000000007,
+ "774": 111.70000000000006,
+ "775": 114.7500000000001,
+ "776": 108.8000000000001,
+ "777": 115.70000000000005,
+ "778": 108.80000000000005,
+ "779": 105.80000000000014,
+ "780": 118.55000000000008,
+ "781": 117.55000000000003,
+ "782": 109.2,
+ "783": 105.94999999999997,
+ "784": 111.50000000000007,
+ "785": 106.54999999999997,
+ "786": 110.2000000000002,
+ "787": 120.40000000000019,
+ "788": 107.80000000000013,
+ "789": 108.80000000000008,
+ "790": 117.50000000000011,
+ "791": 110.90000000000018,
+ "792": 113.79999999999994,
+ "793": 107.50000000000001,
+ "794": 111.15000000000006,
+ "795": 110.05000000000011,
+ "796": 117.9000000000001,
+ "797": 103.04999999999997,
+ "798": 113.70000000000009,
+ "799": 112.10000000000001,
+ "800": 112.15000000000013,
+ "801": 117.00000000000004,
+ "802": 108.49999999999997,
+ "803": 117.75000000000011,
+ "804": 99.35000000000002,
+ "805": 105.7000000000001,
+ "806": 107.90000000000012,
+ "807": 118.55000000000013,
+ "808": 110.80000000000013,
+ "809": 104.3500000000001,
+ "810": 109.60000000000021,
+ "811": 107.20000000000005,
+ "812": 112.50000000000004,
+ "813": 115.65000000000016,
+ "814": 113.49999999999999,
+ "815": 114.05000000000001,
+ "816": 114.29999999999998,
+ "817": 111.80000000000008,
+ "818": 112.15000000000003,
+ "819": 117.15000000000013,
+ "820": 99.10000000000008,
+ "821": 115.45000000000006,
+ "822": 112.74999999999999,
+ "823": 120.70000000000006,
+ "824": 112.35000000000008,
+ "825": 102.64999999999998,
+ "826": 112.79999999999991,
+ "827": 89.00000000000001,
+ "828": 113.04999999999998,
+ "829": 111.60000000000002,
+ "830": 113.30000000000004,
+ "831": 107.45000000000003,
+ "832": 113.70000000000012,
+ "833": 105.40000000000008,
+ "834": 111.05000000000008,
+ "835": 109.5,
+ "836": 84.89999999999995,
+ "837": 115.35000000000011,
+ "838": 114.65000000000009,
+ "839": 111.35000000000008,
+ "840": 106.0000000000002,
+ "841": 115.05000000000014,
+ "842": 113.74999999999996,
+ "843": 111.85000000000008,
+ "844": 114.14999999999998,
+ "845": 115.10000000000011,
+ "846": 107.70000000000005,
+ "847": 107.35000000000007,
+ "848": 109.85000000000007,
+ "849": 100.64999999999999,
+ "850": 109.00000000000001,
+ "851": 108.35000000000007,
+ "852": 116.60000000000008,
+ "853": 109.50000000000006,
+ "854": 36.79999999999978,
+ "855": 116.90000000000009,
+ "856": 105.74999999999999,
+ "857": 107.64999999999996,
+ "858": 113.59999999999994,
+ "859": 121.90000000000006,
+ "860": 107.30000000000004,
+ "861": 95.99999999999991,
+ "862": 104.75000000000018,
+ "863": 104.50000000000017,
+ "864": 111.95000000000007,
+ "865": 114.14999999999999,
+ "866": 101.84999999999987,
+ "867": 114.70000000000014,
+ "868": 108.25000000000004,
+ "869": 110.99999999999994,
+ "870": 113.20000000000014,
+ "871": 105.35000000000016,
+ "872": 113.25000000000023,
+ "873": 120.90000000000005,
+ "874": 97.5,
+ "875": 110.20000000000017,
+ "876": 121.65000000000016,
+ "877": 108.49999999999997,
+ "878": 112.25000000000003,
+ "879": 113.20000000000007,
+ "880": 113.55000000000004,
+ "881": 111.85,
+ "882": 102.9000000000001,
+ "883": 108.85000000000011,
+ "884": 100.9499999999999,
+ "885": 98.25000000000006,
+ "886": 74.8000000000001,
+ "887": 112.45000000000007,
+ "888": 114.55000000000014,
+ "889": 109.74999999999996,
+ "890": 109.9000000000001,
+ "891": 109.60000000000011,
+ "892": 111.85,
+ "893": 76.34999999999995,
+ "894": 117.60000000000005,
+ "895": 112.90000000000016,
+ "896": 109.40000000000015,
+ "897": 111.55000000000015,
+ "898": 111.45000000000013,
+ "899": 121.05000000000021,
+ "900": 113.90000000000006,
+ "901": 103.45000000000016,
+ "902": 110.55000000000015,
+ "903": 105.50000000000009,
+ "904": 119.6000000000001,
+ "905": 117.05000000000013,
+ "906": 111.30000000000015,
+ "907": 110.2500000000002,
+ "908": 104.29999999999998,
+ "909": 114.00000000000003,
+ "910": 115.50000000000003,
+ "911": 110.10000000000004,
+ "912": 113.60000000000021,
+ "913": 115.75000000000004,
+ "914": 108.70000000000007,
+ "915": 116.5500000000001,
+ "916": 106.69999999999993,
+ "917": 115.40000000000015,
+ "918": 105.85000000000012,
+ "919": 109.80000000000022,
+ "920": 112.35000000000018,
+ "921": 115.75000000000003,
+ "922": 115.90000000000013,
+ "923": 113.35000000000008,
+ "924": 105.44999999999999,
+ "925": 110.19999999999996,
+ "926": 122.05000000000004,
+ "927": 115.20000000000003,
+ "928": 117.15000000000002,
+ "929": 102.89999999999998,
+ "930": 116.00000000000007,
+ "931": 99.39999999999993,
+ "932": 111.50000000000006,
+ "933": 113.40000000000005,
+ "934": 108.04999999999988,
+ "935": 122.70000000000005,
+ "936": 115.9500000000001,
+ "937": 107.70000000000005,
+ "938": 113.6,
+ "939": 114.20000000000005,
+ "940": 116.40000000000005,
+ "941": 112.55000000000024,
+ "942": 115.35000000000005,
+ "943": 116.35000000000005,
+ "944": 115.4000000000001,
+ "945": 117.35000000000011,
+ "946": 110.20000000000002,
+ "947": 111.90000000000006,
+ "948": 110.35000000000008,
+ "949": 107.80000000000003,
+ "950": 121.10000000000008,
+ "951": 111.25000000000014,
+ "952": 116.64999999999998,
+ "953": 114.64999999999999,
+ "954": 118.20000000000016,
+ "955": 104.00000000000014,
+ "956": 117.5000000000001,
+ "957": 103.4500000000001,
+ "958": 103.95000000000017,
+ "959": 120.30000000000008,
+ "960": 110.85000000000014,
+ "961": 120.8000000000001,
+ "962": 104.49999999999991,
+ "963": 107.89999999999993,
+ "964": 106.65000000000006,
+ "965": 95.65000000000006,
+ "966": 113.90000000000018,
+ "967": 115.7500000000002,
+ "968": 116.65000000000022,
+ "969": 114.5500000000001,
+ "970": 117.50000000000007,
+ "971": 107.8000000000001,
+ "972": 114.20000000000012,
+ "973": 112.55000000000005,
+ "974": 109.95000000000017,
+ "975": 112.9,
+ "976": 117.49999999999997,
+ "977": 102.30000000000013,
+ "978": 110.35000000000008,
+ "979": 110.75000000000016,
+ "980": 110.70000000000014,
+ "981": 42.09999999999988,
+ "982": 107.09999999999995,
+ "983": 112.10000000000012,
+ "984": 115.3500000000002,
+ "985": 114.99999999999994,
+ "986": 114.10000000000007,
+ "987": 112.05,
+ "988": 117.50000000000003,
+ "989": 110.34999999999992,
+ "990": 115.39999999999995,
+ "991": 119.55,
+ "992": 101.05,
+ "993": 106.30000000000003,
+ "994": 109.95000000000003,
+ "995": 116.6500000000001,
+ "996": 106.7000000000001,
+ "997": 112.15000000000003,
+ "998": 111.70000000000016,
+ "999": 114.9500000000001,
+ "1000": 113.55000000000008
+ },
+ "4": {
+ "1": -27.849999999999984,
+ "2": -20.19999999999996,
+ "3": -43.850000000000044,
+ "4": -94.6,
+ "5": -82.85,
+ "6": -42.600000000000065,
+ "7": -0.699999999999982,
+ "8": -42.00000000000016,
+ "9": -19.949999999999964,
+ "10": -62.9500000000001,
+ "11": -21.449999999999957,
+ "12": -16.14999999999998,
+ "13": -12.399999999999993,
+ "14": -16.94999999999999,
+ "15": -12.849999999999977,
+ "16": -99.90000000000003,
+ "17": -57.10000000000016,
+ "18": -20.49999999999999,
+ "19": -24.749999999999947,
+ "20": -60.2500000000001,
+ "21": -19.749999999999964,
+ "22": -21.749999999999986,
+ "23": -32.85000000000003,
+ "24": -23.49999999999995,
+ "25": -22.949999999999953,
+ "26": -21.699999999999957,
+ "27": -22.699999999999953,
+ "28": -28.39999999999999,
+ "29": -23.899999999999988,
+ "30": -16.99999999999999,
+ "31": -38.10000000000005,
+ "32": -39.84999999999998,
+ "33": -87.1,
+ "34": -23.54999999999995,
+ "35": -33.15000000000001,
+ "36": -11.949999999999994,
+ "37": -98.95,
+ "38": -0.7499999999999756,
+ "39": -4.200000000000005,
+ "40": -27.79999999999998,
+ "41": -13.799999999999981,
+ "42": -91.80000000000001,
+ "43": -8.75,
+ "44": -17.149999999999974,
+ "45": -16.799999999999976,
+ "46": -75.85,
+ "47": -64.89999999999998,
+ "48": -7.699999999999995,
+ "49": -18.599999999999973,
+ "50": -16.399999999999977,
+ "51": -15.14999999999997,
+ "52": -33.54999999999994,
+ "53": -12.749999999999982,
+ "54": -23.24999999999995,
+ "55": -4.899999999999997,
+ "56": -103.94999999999997,
+ "57": -10.299999999999986,
+ "58": -16.549999999999958,
+ "59": -20.94999999999996,
+ "60": -44.500000000000206,
+ "61": -45.65000000000006,
+ "62": -97.44999999999999,
+ "63": -84.00000000000001,
+ "64": -12.94999999999999,
+ "65": -43.70000000000007,
+ "66": -56.05000000000009,
+ "67": -15.49999999999998,
+ "68": -1.6500000000000024,
+ "69": -20.149999999999963,
+ "70": -66.49999999999999,
+ "71": -17.949999999999974,
+ "72": -21.949999999999978,
+ "73": -13.399999999999983,
+ "74": -23.099999999999973,
+ "75": -46.45,
+ "76": -11.399999999999965,
+ "77": -62.6,
+ "78": -15.799999999999978,
+ "79": -18.599999999999966,
+ "80": -30.79999999999999,
+ "81": -13.999999999999993,
+ "82": -55.150000000000084,
+ "83": -13.599999999999987,
+ "84": -19.899999999999963,
+ "85": -4.899999999999988,
+ "86": -37.35000000000001,
+ "87": -17.699999999999967,
+ "88": -7.949999999999993,
+ "89": -12.799999999999988,
+ "90": -61.1,
+ "91": -16.399999999999974,
+ "92": -17.79999999999997,
+ "93": -18.649999999999967,
+ "94": -90.8,
+ "95": -49.900000000000034,
+ "96": -92.10000000000012,
+ "97": -54.899999999999984,
+ "98": -70.19999999999999,
+ "99": -16.849999999999973,
+ "100": 7.149999999999957,
+ "101": -17.299999999999972,
+ "102": -0.15000000000000413,
+ "103": -89.95,
+ "104": -14.49999999999998,
+ "105": -79.75000000000001,
+ "106": -21.699999999999957,
+ "107": -46.30000000000007,
+ "108": -49.450000000000074,
+ "109": -64.05,
+ "110": -7.749999999999987,
+ "111": -78.19999999999999,
+ "112": -15.149999999999979,
+ "113": -22.84999999999995,
+ "114": -25.999999999999936,
+ "115": -18.849999999999966,
+ "116": -10.09999999999999,
+ "117": -19.899999999999963,
+ "118": -19.200000000000003,
+ "119": -77.65,
+ "120": -29.999999999999957,
+ "121": -24.099999999999948,
+ "122": -9.59999999999999,
+ "123": -16.89999999999998,
+ "124": -82.79999999999998,
+ "125": -16.299999999999976,
+ "126": -79.80000000000001,
+ "127": 14.10000000000002,
+ "128": -18.949999999999967,
+ "129": 3.2500000000000355,
+ "130": 8.950000000000031,
+ "131": -62.30000000000001,
+ "132": 23.949999999999804,
+ "133": -53.64999999999997,
+ "134": 3.550000000000032,
+ "135": -82.7,
+ "136": -3.75000000000004,
+ "137": -44.9000000000001,
+ "138": -19.149999999999967,
+ "139": -3.2499999999999902,
+ "140": -1.4499999999999775,
+ "141": -32.54999999999996,
+ "142": -50.09999999999998,
+ "143": -13.39999999999998,
+ "144": -75.9,
+ "145": 21.1,
+ "146": -93.9,
+ "147": -26.650000000000013,
+ "148": -20.79999999999996,
+ "149": 1.5500000000000431,
+ "150": -2.2999999999999785,
+ "151": -36.2,
+ "152": 9.100000000000005,
+ "153": -87.0,
+ "154": 27.199999999999854,
+ "155": 22.099999999999937,
+ "156": -54.85000000000009,
+ "157": -14.849999999999982,
+ "158": 1.4000000000000374,
+ "159": -18.600000000000016,
+ "160": -6.5000000000000115,
+ "161": -19.749999999999964,
+ "162": 18.050000000000004,
+ "163": -88.64999999999998,
+ "164": 7.05,
+ "165": -2.7499999999999547,
+ "166": 12.25,
+ "167": 28.54999999999984,
+ "168": 8.050000000000054,
+ "169": 31.749999999999893,
+ "170": -4.5000000000000115,
+ "171": -44.70000000000005,
+ "172": -86.25,
+ "173": -41.39999999999997,
+ "174": -54.45000000000003,
+ "175": -8.599999999999985,
+ "176": -58.3000000000001,
+ "177": -96.85,
+ "178": 25.699999999999868,
+ "179": -15.49999999999998,
+ "180": 8.650000000000034,
+ "181": -1.4499999999999686,
+ "182": -24.599999999999977,
+ "183": -15.900000000000002,
+ "184": -39.89999999999997,
+ "185": 24.84999999999995,
+ "186": -73.94999999999999,
+ "187": -84.0,
+ "188": -20.099999999999962,
+ "189": -84.30000000000001,
+ "190": -66.29999999999995,
+ "191": -14.200000000000003,
+ "192": -22.99999999999995,
+ "193": -38.64999999999999,
+ "194": 1.6500000000000559,
+ "195": -9.349999999999993,
+ "196": -0.29999999999998206,
+ "197": -67.14999999999993,
+ "198": -15.649999999999968,
+ "199": -9.049999999999985,
+ "200": -0.39999999999999075,
+ "201": -3.650000000000004,
+ "202": -84.20000000000005,
+ "203": -41.65000000000002,
+ "204": -30.4,
+ "205": -7.099999999999993,
+ "206": -17.899999999999967,
+ "207": 31.35000000000005,
+ "208": 54.84999999999998,
+ "209": 2.1499999999999986,
+ "210": -8.699999999999996,
+ "211": -3.7999999999999705,
+ "212": -9.850000000000001,
+ "213": -11.39999999999999,
+ "214": 82.55000000000015,
+ "215": -3.1499999999999835,
+ "216": -3.749999999999977,
+ "217": -26.049999999999933,
+ "218": 19.150000000000027,
+ "219": -73.49999999999999,
+ "220": -10.499999999999996,
+ "221": 7.300000000000016,
+ "222": 24.39999999999993,
+ "223": 68.24999999999989,
+ "224": -10.549999999999986,
+ "225": -59.049999999999955,
+ "226": 29.30000000000004,
+ "227": -8.900000000000048,
+ "228": 70.25000000000017,
+ "229": 35.84999999999979,
+ "230": 31.499999999999908,
+ "231": 25.69999999999991,
+ "232": -40.75000000000002,
+ "233": 3.799999999999967,
+ "234": -32.249999999999986,
+ "235": -23.850000000000016,
+ "236": 11.849999999999973,
+ "237": -13.750000000000036,
+ "238": 56.59999999999997,
+ "239": -54.79999999999998,
+ "240": 20.54999999999994,
+ "241": 34.499999999999915,
+ "242": -4.2000000000000295,
+ "243": 26.349999999999984,
+ "244": 63.39999999999985,
+ "245": 66.59999999999991,
+ "246": 35.49999999999987,
+ "247": 98.35000000000011,
+ "248": -76.49999999999997,
+ "249": 12.999999999999993,
+ "250": -31.25000000000002,
+ "251": 51.64999999999985,
+ "252": 32.40000000000004,
+ "253": 98.05000000000004,
+ "254": 96.55000000000013,
+ "255": 46.899999999999935,
+ "256": 39.34999999999984,
+ "257": 107.40000000000003,
+ "258": 32.25,
+ "259": 26.149999999999963,
+ "260": 15.199999999999967,
+ "261": 65.15000000000005,
+ "262": 5.849999999999987,
+ "263": -76.4,
+ "264": 42.74999999999981,
+ "265": 105.80000000000015,
+ "266": 0.8500000000000099,
+ "267": 23.29999999999999,
+ "268": 7.300000000000004,
+ "269": 12.000000000000046,
+ "270": 29.14999999999996,
+ "271": 39.25000000000002,
+ "272": 39.700000000000045,
+ "273": -4.25,
+ "274": 99.95000000000007,
+ "275": 100.30000000000005,
+ "276": 1.5499999999999357,
+ "277": 32.45000000000009,
+ "278": 93.29999999999998,
+ "279": 95.09999999999995,
+ "280": 43.94999999999997,
+ "281": 52.39999999999987,
+ "282": 9.299999999999976,
+ "283": -26.799999999999933,
+ "284": 3.4999999999999867,
+ "285": 100.55000000000008,
+ "286": 118.00000000000014,
+ "287": 48.600000000000016,
+ "288": 7.299999999999976,
+ "289": 36.000000000000036,
+ "290": 59.04999999999984,
+ "291": 45.49999999999999,
+ "292": 38.099999999999994,
+ "293": 22.699999999999967,
+ "294": 57.50000000000001,
+ "295": 95.25000000000006,
+ "296": 106.75000000000004,
+ "297": 84.50000000000021,
+ "298": 33.04999999999999,
+ "299": 108.65000000000015,
+ "300": 90.50000000000013,
+ "301": 102.30000000000007,
+ "302": 67.94999999999999,
+ "303": 112.8500000000002,
+ "304": 107.70000000000009,
+ "305": 65.2999999999999,
+ "306": 101.15,
+ "307": 99.50000000000011,
+ "308": 84.70000000000012,
+ "309": 100.75,
+ "310": 52.04999999999992,
+ "311": 53.00000000000002,
+ "312": 77.84999999999997,
+ "313": 57.849999999999945,
+ "314": 49.04999999999982,
+ "315": 101.19999999999997,
+ "316": -2.8500000000000174,
+ "317": 48.84999999999991,
+ "318": 90.19999999999997,
+ "319": 60.84999999999994,
+ "320": 40.349999999999916,
+ "321": 103.65000000000009,
+ "322": 97.0,
+ "323": -35.549999999999976,
+ "324": -16.34999999999998,
+ "325": 59.050000000000004,
+ "326": 96.94999999999992,
+ "327": 71.49999999999996,
+ "328": 47.79999999999999,
+ "329": 106.65000000000016,
+ "330": 93.5000000000001,
+ "331": 109.5500000000001,
+ "332": 60.50000000000006,
+ "333": 80.10000000000001,
+ "334": 96.45000000000013,
+ "335": 52.35000000000002,
+ "336": 104.70000000000012,
+ "337": 83.65000000000005,
+ "338": -10.900000000000036,
+ "339": 98.80000000000004,
+ "340": 59.39999999999985,
+ "341": 73.84999999999985,
+ "342": 79.05000000000001,
+ "343": 109.85000000000008,
+ "344": 97.10000000000001,
+ "345": 50.749999999999886,
+ "346": 112.00000000000001,
+ "347": 107.85000000000011,
+ "348": 88.95,
+ "349": 97.00000000000009,
+ "350": 109.20000000000012,
+ "351": 47.8499999999999,
+ "352": 113.3500000000001,
+ "353": 108.1000000000001,
+ "354": 95.55000000000008,
+ "355": 88.74999999999999,
+ "356": 64.64999999999986,
+ "357": 98.75000000000007,
+ "358": 110.49999999999994,
+ "359": 84.10000000000002,
+ "360": 97.75000000000014,
+ "361": 88.50000000000003,
+ "362": 102.40000000000005,
+ "363": 55.19999999999976,
+ "364": 93.55000000000001,
+ "365": 117.20000000000006,
+ "366": 98.9,
+ "367": 90.85000000000002,
+ "368": 99.95000000000006,
+ "369": 100.29999999999995,
+ "370": 13.700000000000049,
+ "371": 116.60000000000007,
+ "372": 111.25,
+ "373": 110.20000000000013,
+ "374": 69.29999999999987,
+ "375": 112.25000000000007,
+ "376": 113.05000000000004,
+ "377": 87.50000000000003,
+ "378": 32.549999999999976,
+ "379": 105.95000000000013,
+ "380": 56.84999999999989,
+ "381": 89.44999999999996,
+ "382": 86.74999999999994,
+ "383": 105.0,
+ "384": 104.99999999999999,
+ "385": 106.80000000000011,
+ "386": 108.00000000000014,
+ "387": 47.800000000000004,
+ "388": 103.35,
+ "389": 90.85000000000007,
+ "390": 95.55000000000003,
+ "391": 100.5500000000001,
+ "392": 111.59999999999994,
+ "393": 117.75000000000023,
+ "394": 103.65000000000012,
+ "395": 110.90000000000013,
+ "396": 106.8,
+ "397": 111.10000000000024,
+ "398": 87.20000000000006,
+ "399": 113.15000000000012,
+ "400": 99.8500000000001,
+ "401": 115.85000000000012,
+ "402": 88.15000000000029,
+ "403": 114.40000000000019,
+ "404": 110.4000000000002,
+ "405": 115.4000000000002,
+ "406": 117.00000000000007,
+ "407": 89.25000000000004,
+ "408": 103.9,
+ "409": 95.60000000000004,
+ "410": 115.2500000000001,
+ "411": 118.54999999999997,
+ "412": 102.40000000000013,
+ "413": 108.89999999999998,
+ "414": 53.34999999999994,
+ "415": 101.89999999999999,
+ "416": 113.85000000000016,
+ "417": 110.94999999999996,
+ "418": 90.90000000000002,
+ "419": 111.7999999999999,
+ "420": 114.85000000000014,
+ "421": 104.30000000000022,
+ "422": 97.24999999999989,
+ "423": 113.10000000000018,
+ "424": 106.69999999999993,
+ "425": 93.95000000000013,
+ "426": 93.54999999999998,
+ "427": -0.9499999999999846,
+ "428": 107.85000000000008,
+ "429": 110.75,
+ "430": 113.7000000000001,
+ "431": 101.5000000000001,
+ "432": 111.75000000000018,
+ "433": 112.1500000000002,
+ "434": 115.90000000000003,
+ "435": 118.50000000000011,
+ "436": 113.30000000000008,
+ "437": 104.40000000000002,
+ "438": 112.0500000000002,
+ "439": 108.30000000000001,
+ "440": 116.80000000000021,
+ "441": 109.5000000000002,
+ "442": 74.7000000000001,
+ "443": 118.40000000000008,
+ "444": 103.15000000000009,
+ "445": 112.85000000000007,
+ "446": 102.05000000000003,
+ "447": 99.35000000000002,
+ "448": 99.70000000000003,
+ "449": 104.05000000000004,
+ "450": 107.20000000000003,
+ "451": 110.90000000000015,
+ "452": 103.95000000000007,
+ "453": 93.70000000000005,
+ "454": 113.90000000000006,
+ "455": 84.45000000000009,
+ "456": 105.89999999999993,
+ "457": 109.30000000000003,
+ "458": 105.95000000000007,
+ "459": 94.44999999999997,
+ "460": 107.60000000000004,
+ "461": 114.6000000000001,
+ "462": 111.39999999999996,
+ "463": 104.90000000000002,
+ "464": 108.65000000000008,
+ "465": 119.75000000000001,
+ "466": 108.69999999999993,
+ "467": 109.14999999999999,
+ "468": 113.25000000000013,
+ "469": 107.4000000000002,
+ "470": 120.04999999999998,
+ "471": 110.30000000000001,
+ "472": 109.60000000000011,
+ "473": 106.75000000000004,
+ "474": 112.05000000000014,
+ "475": 106.6500000000001,
+ "476": 98.10000000000011,
+ "477": 114.35000000000002,
+ "478": 108.99999999999996,
+ "479": 113.10000000000004,
+ "480": 88.15000000000012,
+ "481": 117.95000000000005,
+ "482": 114.80000000000018,
+ "483": 111.80000000000025,
+ "484": 116.20000000000023,
+ "485": 111.04999999999993,
+ "486": 74.5000000000001,
+ "487": 117.80000000000018,
+ "488": 102.95000000000014,
+ "489": 112.65000000000005,
+ "490": 111.4500000000001,
+ "491": 99.9999999999999,
+ "492": 111.55000000000004,
+ "493": 103.00000000000009,
+ "494": 113.20000000000016,
+ "495": 109.35000000000001,
+ "496": 105.74999999999997,
+ "497": 119.55000000000003,
+ "498": 121.00000000000009,
+ "499": 116.20000000000007,
+ "500": 114.00000000000001,
+ "501": 114.25000000000007,
+ "502": 108.3000000000001,
+ "503": 108.40000000000008,
+ "504": 118.25000000000013,
+ "505": 102.65000000000012,
+ "506": 118.05000000000011,
+ "507": 121.0000000000001,
+ "508": 115.40000000000009,
+ "509": 104.95000000000012,
+ "510": 119.25000000000009,
+ "511": 88.05000000000014,
+ "512": 113.64999999999995,
+ "513": -9.449999999999994,
+ "514": 117.0000000000001,
+ "515": 109.44999999999995,
+ "516": 112.49999999999996,
+ "517": 109.80000000000008,
+ "518": 45.999999999999766,
+ "519": 117.45000000000007,
+ "520": 121.05000000000005,
+ "521": 114.70000000000019,
+ "522": 101.10000000000004,
+ "523": 109.95000000000009,
+ "524": 111.50000000000011,
+ "525": 103.70000000000007,
+ "526": 62.849999999999866,
+ "527": 117.05000000000008,
+ "528": 108.35000000000005,
+ "529": 109.44999999999999,
+ "530": 102.65000000000008,
+ "531": 115.84999999999998,
+ "532": 40.449999999999946,
+ "533": 114.25,
+ "534": 105.90000000000008,
+ "535": 91.1,
+ "536": 102.90000000000012,
+ "537": 97.70000000000012,
+ "538": -14.749999999999975,
+ "539": 112.30000000000005,
+ "540": 105.70000000000012,
+ "541": 104.90000000000006,
+ "542": 93.19999999999987,
+ "543": 110.65000000000018,
+ "544": 117.10000000000007,
+ "545": 112.10000000000021,
+ "546": 110.69999999999999,
+ "547": 50.04999999999984,
+ "548": 109.45000000000003,
+ "549": 67.69999999999989,
+ "550": 109.3,
+ "551": -14.849999999999977,
+ "552": 47.599999999999895,
+ "553": 74.44999999999989,
+ "554": 38.749999999999936,
+ "555": 52.89999999999991,
+ "556": 53.149999999999956,
+ "557": 65.99999999999996,
+ "558": 87.89999999999999,
+ "559": 66.95000000000003,
+ "560": 41.39999999999986,
+ "561": 78.54999999999998,
+ "562": 74.94999999999993,
+ "563": 44.64999999999988,
+ "564": 96.95000000000003,
+ "565": 16.799999999999997,
+ "566": 80.05000000000013,
+ "567": -7.500000000000012,
+ "568": 56.099999999999916,
+ "569": 49.14999999999993,
+ "570": 103.89999999999998,
+ "571": 71.84999999999995,
+ "572": 101.59999999999994,
+ "573": 78.19999999999999,
+ "574": 47.04999999999983,
+ "575": 39.099999999999916,
+ "576": 94.80000000000005,
+ "577": 93.65000000000013,
+ "578": 94.65000000000019,
+ "579": 38.59999999999996,
+ "580": 106.75000000000011,
+ "581": 57.049999999999855,
+ "582": 37.09999999999987,
+ "583": 92.34999999999997,
+ "584": 98.70000000000027,
+ "585": 79.40000000000002,
+ "586": 112.65000000000015,
+ "587": 107.74999999999997,
+ "588": 106.4999999999999,
+ "589": 88.45000000000005,
+ "590": 106.85000000000004,
+ "591": 88.09999999999995,
+ "592": 90.50000000000003,
+ "593": 68.65000000000002,
+ "594": 30.249999999999954,
+ "595": 85.75000000000001,
+ "596": 91.35000000000001,
+ "597": 74.64999999999998,
+ "598": 96.95000000000007,
+ "599": 101.70000000000009,
+ "600": 91.84999999999995,
+ "601": 112.85000000000011,
+ "602": 95.20000000000014,
+ "603": -12.149999999999961,
+ "604": 113.25000000000007,
+ "605": 114.85000000000024,
+ "606": 92.29999999999998,
+ "607": 80.69999999999997,
+ "608": 113.00000000000013,
+ "609": 116.95000000000013,
+ "610": 92.95000000000012,
+ "611": 73.05,
+ "612": 81.79999999999988,
+ "613": 97.90000000000002,
+ "614": 105.2,
+ "615": 112.40000000000005,
+ "616": 94.55000000000001,
+ "617": 109.55000000000014,
+ "618": 105.40000000000009,
+ "619": 101.2,
+ "620": 98.65,
+ "621": 74.19999999999997,
+ "622": 107.50000000000003,
+ "623": 99.3500000000001,
+ "624": 102.05,
+ "625": 118.1,
+ "626": 105.10000000000004,
+ "627": 112.95000000000005,
+ "628": 93.44999999999999,
+ "629": 100.10000000000007,
+ "630": 91.40000000000009,
+ "631": 112.15000000000016,
+ "632": 110.75000000000003,
+ "633": 105.90000000000002,
+ "634": 91.60000000000005,
+ "635": 49.34999999999979,
+ "636": 105.05000000000007,
+ "637": 119.40000000000003,
+ "638": 110.70000000000019,
+ "639": 109.85000000000016,
+ "640": 63.8,
+ "641": 109.79999999999997,
+ "642": 109.34999999999998,
+ "643": 117.1000000000002,
+ "644": 118.75,
+ "645": 111.10000000000011,
+ "646": 103.95000000000012,
+ "647": 34.14999999999991,
+ "648": 106.75000000000004,
+ "649": 81.2500000000001,
+ "650": 105.95,
+ "651": 112.80000000000007,
+ "652": 118.25000000000004,
+ "653": 115.55000000000011,
+ "654": 112.9000000000001,
+ "655": 107.55,
+ "656": 117.55000000000013,
+ "657": 99.15000000000015,
+ "658": 113.1500000000001,
+ "659": 106.45000000000005,
+ "660": 103.55000000000008,
+ "661": 102.30000000000008,
+ "662": 107.10000000000005,
+ "663": 103.70000000000007,
+ "664": 61.949999999999875,
+ "665": 102.85,
+ "666": 104.20000000000017,
+ "667": 110.30000000000003,
+ "668": 115.85000000000015,
+ "669": 109.4500000000001,
+ "670": 107.60000000000012,
+ "671": 116.00000000000014,
+ "672": 114.70000000000013,
+ "673": 114.80000000000005,
+ "674": 113.90000000000015,
+ "675": 117.5500000000001,
+ "676": 21.89999999999997,
+ "677": 109.10000000000022,
+ "678": 112.44999999999993,
+ "679": 116.95000000000006,
+ "680": 115.20000000000009,
+ "681": 103.70000000000006,
+ "682": 100.65000000000006,
+ "683": 105.54999999999991,
+ "684": 99.29999999999995,
+ "685": 105.35000000000015,
+ "686": 28.849999999999866,
+ "687": 116.35000000000001,
+ "688": 109.20000000000012,
+ "689": 114.95000000000003,
+ "690": 112.9000000000002,
+ "691": 106.55000000000014,
+ "692": 110.90000000000018,
+ "693": 108.95000000000017,
+ "694": 113.85000000000007,
+ "695": 111.6500000000001,
+ "696": 100.89999999999986,
+ "697": 106.49999999999997,
+ "698": 115.30000000000004,
+ "699": 112.30000000000008,
+ "700": 109.10000000000008,
+ "701": 110.10000000000014,
+ "702": 107.25000000000001,
+ "703": 109.75000000000009,
+ "704": 108.1999999999999,
+ "705": 112.75000000000014,
+ "706": 100.0,
+ "707": 109.70000000000009,
+ "708": 111.35000000000002,
+ "709": 110.65000000000008,
+ "710": 109.80000000000022,
+ "711": 111.25000000000014,
+ "712": 107.90000000000005,
+ "713": 105.4500000000001,
+ "714": 115.1,
+ "715": 110.25000000000017,
+ "716": 109.90000000000015,
+ "717": 108.75000000000006,
+ "718": 114.75000000000013,
+ "719": 113.10000000000004,
+ "720": 116.20000000000022,
+ "721": 110.8,
+ "722": 118.50000000000007,
+ "723": 112.00000000000001,
+ "724": 116.10000000000026,
+ "725": 111.84999999999992,
+ "726": 109.00000000000006,
+ "727": 109.39999999999999,
+ "728": 112.74999999999994,
+ "729": 115.35000000000008,
+ "730": 114.10000000000008,
+ "731": 113.35000000000001,
+ "732": 110.25000000000004,
+ "733": 109.09999999999997,
+ "734": 116.00000000000018,
+ "735": 115.40000000000012,
+ "736": 106.20000000000002,
+ "737": 113.3500000000001,
+ "738": 119.35000000000025,
+ "739": 115.15,
+ "740": 110.45000000000006,
+ "741": 107.55000000000014,
+ "742": 103.65000000000002,
+ "743": 97.55,
+ "744": 114.60000000000022,
+ "745": 108.19999999999992,
+ "746": 105.10000000000004,
+ "747": 109.50000000000004,
+ "748": 102.50000000000009,
+ "749": 122.3,
+ "750": 107.3,
+ "751": 107.15000000000016,
+ "752": 111.4000000000001,
+ "753": 114.35000000000011,
+ "754": 105.85000000000001,
+ "755": 110.45000000000013,
+ "756": 101.79999999999998,
+ "757": 112.00000000000011,
+ "758": 73.95000000000002,
+ "759": 110.50000000000009,
+ "760": 116.70000000000022,
+ "761": 117.10000000000018,
+ "762": 112.2,
+ "763": 90.60000000000018,
+ "764": 107.99999999999997,
+ "765": 112.35000000000012,
+ "766": 111.50000000000006,
+ "767": 110.10000000000005,
+ "768": 109.35000000000011,
+ "769": 107.40000000000009,
+ "770": 92.65000000000002,
+ "771": 112.45000000000005,
+ "772": 105.55,
+ "773": 106.04999999999987,
+ "774": 111.55000000000004,
+ "775": 101.25000000000003,
+ "776": 116.35000000000011,
+ "777": 117.30000000000004,
+ "778": 114.00000000000001,
+ "779": 115.40000000000018,
+ "780": 112.75000000000004,
+ "781": 117.2000000000001,
+ "782": 118.3000000000001,
+ "783": 108.85000000000008,
+ "784": 109.09999999999998,
+ "785": 109.00000000000009,
+ "786": 112.1500000000001,
+ "787": 111.49999999999996,
+ "788": 112.45000000000009,
+ "789": 113.60000000000014,
+ "790": 113.45000000000016,
+ "791": 98.10000000000002,
+ "792": 114.00000000000007,
+ "793": 114.8000000000001,
+ "794": 107.50000000000004,
+ "795": 109.20000000000005,
+ "796": 104.85000000000002,
+ "797": 107.40000000000012,
+ "798": 112.70000000000013,
+ "799": 107.70000000000007,
+ "800": 116.0500000000002,
+ "801": 102.80000000000007,
+ "802": 112.45000000000017,
+ "803": 113.85000000000007,
+ "804": 110.50000000000014,
+ "805": 111.35000000000016,
+ "806": 107.25000000000009,
+ "807": 113.10000000000015,
+ "808": 103.80000000000004,
+ "809": 107.60000000000007,
+ "810": 112.35000000000011,
+ "811": 109.60000000000004,
+ "812": 111.85,
+ "813": 106.35000000000011,
+ "814": 60.149999999999906,
+ "815": 108.55000000000015,
+ "816": 102.60000000000011,
+ "817": 112.65,
+ "818": 111.00000000000013,
+ "819": 116.15000000000019,
+ "820": 107.64999999999995,
+ "821": 100.05000000000018,
+ "822": 116.9,
+ "823": 111.49999999999997,
+ "824": 101.65000000000006,
+ "825": 113.7500000000001,
+ "826": 111.65000000000022,
+ "827": 107.29999999999997,
+ "828": 106.75000000000004,
+ "829": 112.00000000000009,
+ "830": 111.70000000000005,
+ "831": 111.54999999999998,
+ "832": 110.50000000000006,
+ "833": 114.00000000000009,
+ "834": 100.10000000000011,
+ "835": 107.25000000000007,
+ "836": 112.6500000000001,
+ "837": 110.00000000000016,
+ "838": 111.55000000000001,
+ "839": 105.2000000000001,
+ "840": 102.35,
+ "841": 114.10000000000005,
+ "842": 113.65000000000002,
+ "843": 112.35000000000005,
+ "844": 108.85000000000004,
+ "845": 103.55000000000005,
+ "846": 108.50000000000009,
+ "847": 113.3500000000002,
+ "848": 108.75000000000013,
+ "849": 107.90000000000009,
+ "850": 83.05000000000004,
+ "851": 94.39999999999993,
+ "852": 113.40000000000002,
+ "853": 78.40000000000008,
+ "854": 117.9500000000001,
+ "855": 109.79999999999995,
+ "856": 111.2500000000001,
+ "857": 117.0500000000001,
+ "858": 121.49999999999999,
+ "859": 92.64999999999998,
+ "860": 109.8500000000001,
+ "861": 103.00000000000014,
+ "862": 114.7500000000001,
+ "863": 116.60000000000015,
+ "864": 69.90000000000002,
+ "865": 100.40000000000008,
+ "866": 114.35000000000008,
+ "867": 114.10000000000014,
+ "868": 108.89999999999998,
+ "869": 110.25000000000014,
+ "870": 107.04999999999997,
+ "871": 102.50000000000013,
+ "872": 114.75000000000011,
+ "873": 109.35000000000005,
+ "874": 107.45000000000022,
+ "875": 111.29999999999995,
+ "876": 115.29999999999998,
+ "877": 107.54999999999995,
+ "878": 113.70000000000014,
+ "879": 107.90000000000005,
+ "880": 111.50000000000007,
+ "881": 101.2499999999999,
+ "882": 106.45000000000013,
+ "883": 110.2,
+ "884": 121.50000000000009,
+ "885": 122.40000000000016,
+ "886": 102.69999999999999,
+ "887": 116.15000000000022,
+ "888": 111.40000000000002,
+ "889": 121.05000000000018,
+ "890": 109.85000000000008,
+ "891": 103.40000000000013,
+ "892": 112.95,
+ "893": 110.54999999999998,
+ "894": 82.89999999999992,
+ "895": 121.00000000000004,
+ "896": 113.00000000000014,
+ "897": 107.80000000000004,
+ "898": 115.0000000000001,
+ "899": 104.45000000000003,
+ "900": 116.90000000000012,
+ "901": 114.00000000000001,
+ "902": 116.95000000000006,
+ "903": 111.85000000000007,
+ "904": 109.14999999999993,
+ "905": 109.75000000000014,
+ "906": 106.5000000000001,
+ "907": 117.00000000000014,
+ "908": 111.00000000000006,
+ "909": 110.90000000000002,
+ "910": 100.39999999999999,
+ "911": 106.00000000000014,
+ "912": 99.8000000000001,
+ "913": 117.0500000000001,
+ "914": 103.34999999999997,
+ "915": 102.95,
+ "916": 114.85000000000022,
+ "917": 109.70000000000007,
+ "918": 115.00000000000004,
+ "919": 3.350000000000014,
+ "920": 104.15000000000005,
+ "921": 109.65000000000013,
+ "922": 116.55000000000005,
+ "923": 114.10000000000008,
+ "924": 111.64999999999995,
+ "925": 93.9000000000001,
+ "926": 112.70000000000002,
+ "927": 106.65000000000005,
+ "928": 116.20000000000019,
+ "929": 103.79999999999997,
+ "930": 117.20000000000006,
+ "931": 115.60000000000016,
+ "932": 109.50000000000009,
+ "933": 104.10000000000007,
+ "934": 121.55000000000004,
+ "935": 102.55000000000017,
+ "936": 108.85000000000002,
+ "937": 113.05000000000015,
+ "938": 106.60000000000008,
+ "939": 107.85000000000015,
+ "940": 109.90000000000009,
+ "941": 113.80000000000007,
+ "942": 115.19999999999997,
+ "943": 106.9,
+ "944": 111.75000000000016,
+ "945": 112.90000000000002,
+ "946": 118.40000000000009,
+ "947": 107.39999999999995,
+ "948": 75.25000000000013,
+ "949": 108.40000000000002,
+ "950": 108.95000000000002,
+ "951": 92.10000000000011,
+ "952": 101.44999999999996,
+ "953": 116.2,
+ "954": 115.10000000000011,
+ "955": 114.30000000000015,
+ "956": 113.99999999999993,
+ "957": 115.40000000000003,
+ "958": 101.09999999999997,
+ "959": 117.65000000000003,
+ "960": 113.40000000000012,
+ "961": 110.50000000000018,
+ "962": 46.7999999999999,
+ "963": 113.74999999999991,
+ "964": 113.00000000000007,
+ "965": 109.45000000000003,
+ "966": 112.25000000000016,
+ "967": 113.60000000000011,
+ "968": 105.30000000000003,
+ "969": 113.04999999999997,
+ "970": 119.50000000000007,
+ "971": 113.6,
+ "972": 110.45000000000012,
+ "973": 118.60000000000016,
+ "974": 103.20000000000006,
+ "975": 109.05000000000014,
+ "976": 108.90000000000003,
+ "977": 108.50000000000003,
+ "978": 101.74999999999999,
+ "979": 63.999999999999886,
+ "980": 113.5000000000001,
+ "981": 113.6500000000001,
+ "982": 105.65000000000005,
+ "983": 114.05000000000011,
+ "984": 84.5000000000002,
+ "985": 108.09999999999982,
+ "986": 95.15000000000003,
+ "987": 109.70000000000017,
+ "988": 114.7000000000001,
+ "989": 97.30000000000008,
+ "990": 114.45000000000014,
+ "991": 99.95000000000009,
+ "992": 117.7500000000001,
+ "993": 110.39999999999999,
+ "994": 110.64999999999996,
+ "995": 108.75000000000009,
+ "996": 107.6000000000001,
+ "997": 113.75000000000009,
+ "998": 101.95000000000013,
+ "999": 119.30000000000001,
+ "1000": 107.79999999999997
+ },
+ "5": {
+ "1": -24.64999999999995,
+ "2": 0.6500000000000454,
+ "3": -68.15000000000002,
+ "4": -64.0500000000001,
+ "5": -10.99999999999999,
+ "6": -11.799999999999974,
+ "7": -31.950000000000017,
+ "8": -27.59999999999993,
+ "9": -31.800000000000022,
+ "10": -34.20000000000004,
+ "11": -61.39999999999999,
+ "12": -13.799999999999985,
+ "13": -57.15000000000011,
+ "14": -32.34999999999999,
+ "15": -58.3,
+ "16": -34.950000000000024,
+ "17": -3.050000000000022,
+ "18": -12.599999999999985,
+ "19": -68.20000000000006,
+ "20": -13.349999999999968,
+ "21": -41.00000000000014,
+ "22": -12.39999999999999,
+ "23": -5.299999999999996,
+ "24": -75.75,
+ "25": -41.05000000000003,
+ "26": -15.34999999999998,
+ "27": -26.90000000000002,
+ "28": -5.199999999999991,
+ "29": -7.849999999999986,
+ "30": -10.649999999999988,
+ "31": -20.349999999999962,
+ "32": -29.300000000000026,
+ "33": -10.199999999999992,
+ "34": -7.949999999999997,
+ "35": -20.44999999999996,
+ "36": -25.99999999999996,
+ "37": -15.749999999999972,
+ "38": -11.599999999999987,
+ "39": -14.649999999999974,
+ "40": -81.20000000000006,
+ "41": -34.75000000000002,
+ "42": -16.749999999999975,
+ "43": -17.399999999999974,
+ "44": -21.949999999999957,
+ "45": -19.649999999999967,
+ "46": -18.849999999999962,
+ "47": -16.999999999999975,
+ "48": -66.95000000000009,
+ "49": -11.999999999999988,
+ "50": -16.849999999999973,
+ "51": -15.94999999999997,
+ "52": -17.549999999999955,
+ "53": -44.50000000000006,
+ "54": -6.450000000000003,
+ "55": -17.94999999999997,
+ "56": -14.049999999999981,
+ "57": -14.949999999999966,
+ "58": -5.7499999999999885,
+ "59": -7.749999999999994,
+ "60": -18.499999999999957,
+ "61": -89.45,
+ "62": -21.09999999999996,
+ "63": -19.29999999999996,
+ "64": -17.799999999999972,
+ "65": -13.949999999999989,
+ "66": -94.65,
+ "67": -15.749999999999977,
+ "68": -15.999999999999973,
+ "69": -20.89999999999996,
+ "70": -10.749999999999991,
+ "71": -23.54999999999997,
+ "72": -31.150000000000023,
+ "73": -9.6,
+ "74": 0.6500000000000259,
+ "75": -61.00000000000011,
+ "76": -10.299999999999999,
+ "77": -93.0,
+ "78": -23.19999999999995,
+ "79": -22.40000000000001,
+ "80": -86.09999999999997,
+ "81": -18.899999999999967,
+ "82": -20.04999999999996,
+ "83": -18.399999999999967,
+ "84": -67.90000000000002,
+ "85": -18.099999999999973,
+ "86": -61.6000000000001,
+ "87": -8.050000000000002,
+ "88": -13.949999999999987,
+ "89": -13.399999999999979,
+ "90": -1.4999999999999818,
+ "91": -10.349999999999993,
+ "92": -86.34999999999998,
+ "93": -8.899999999999991,
+ "94": -10.299999999999997,
+ "95": -15.049999999999983,
+ "96": -71.14999999999998,
+ "97": -15.999999999999979,
+ "98": 0.15000000000004343,
+ "99": -21.699999999999957,
+ "100": -3.6999999999999904,
+ "101": -52.29999999999998,
+ "102": -9.65000000000001,
+ "103": -91.69999999999999,
+ "104": -88.99999999999994,
+ "105": -7.649999999999988,
+ "106": -7.09999999999999,
+ "107": 1.550000000000013,
+ "108": -89.89999999999999,
+ "109": -13.299999999999978,
+ "110": -47.0,
+ "111": -17.549999999999972,
+ "112": 8.950000000000017,
+ "113": -19.949999999999996,
+ "114": -2.699999999999993,
+ "115": -87.80000000000008,
+ "116": -91.29999999999998,
+ "117": 18.049999999999976,
+ "118": -19.24999999999996,
+ "119": -47.59999999999996,
+ "120": -6.850000000000003,
+ "121": 0.9500000000000397,
+ "122": -17.59999999999997,
+ "123": -87.8,
+ "124": -1.1499999999999826,
+ "125": -2.049999999999976,
+ "126": -17.799999999999972,
+ "127": -12.099999999999984,
+ "128": -14.249999999999986,
+ "129": -36.60000000000003,
+ "130": -7.149999999999996,
+ "131": -69.49999999999999,
+ "132": -13.999999999999988,
+ "133": -3.3999999999999577,
+ "134": -89.0,
+ "135": -0.8499999999999972,
+ "136": 2.1000000000000068,
+ "137": -13.299999999999976,
+ "138": -7.999999999999987,
+ "139": -5.200000000000035,
+ "140": -48.10000000000006,
+ "141": -99.04999999999998,
+ "142": -7.100000000000006,
+ "143": 1.6000000000000303,
+ "144": 2.599999999999989,
+ "145": -21.499999999999957,
+ "146": -61.849999999999966,
+ "147": -17.94999999999997,
+ "148": -22.19999999999995,
+ "149": -21.549999999999958,
+ "150": -17.99999999999997,
+ "151": -49.349999999999994,
+ "152": -59.40000000000003,
+ "153": -9.349999999999993,
+ "154": 1.500000000000014,
+ "155": 2.099999999999996,
+ "156": -15.399999999999991,
+ "157": -5.59999999999999,
+ "158": -24.14999999999995,
+ "159": -52.35,
+ "160": -19.349999999999966,
+ "161": -42.600000000000115,
+ "162": -27.20000000000001,
+ "163": -11.649999999999986,
+ "164": -61.1000000000001,
+ "165": -18.29999999999997,
+ "166": -100.2,
+ "167": -5.549999999999984,
+ "168": -96.05000000000001,
+ "169": -54.70000000000006,
+ "170": -74.7,
+ "171": -13.349999999999985,
+ "172": -8.149999999999991,
+ "173": -4.149999999999979,
+ "174": -15.299999999999983,
+ "175": -12.399999999999986,
+ "176": 2.40000000000002,
+ "177": -2.3499999999999686,
+ "178": 21.350000000000055,
+ "179": -9.100000000000039,
+ "180": 14.149999999999993,
+ "181": -72.14999999999999,
+ "182": -13.299999999999985,
+ "183": -51.599999999999966,
+ "184": -75.89999999999999,
+ "185": -4.099999999999998,
+ "186": -86.1,
+ "187": -24.549999999999947,
+ "188": -10.949999999999994,
+ "189": 25.94999999999995,
+ "190": -8.150000000000004,
+ "191": -2.499999999999975,
+ "192": -87.1,
+ "193": -53.39999999999999,
+ "194": -45.50000000000007,
+ "195": -45.80000000000006,
+ "196": -0.09999999999998921,
+ "197": 12.099999999999993,
+ "198": -74.50000000000003,
+ "199": -62.60000000000003,
+ "200": -25.85000000000002,
+ "201": -69.69999999999999,
+ "202": -28.649999999999995,
+ "203": -18.29999999999997,
+ "204": 16.25000000000004,
+ "205": -18.399999999999967,
+ "206": 8.500000000000036,
+ "207": -88.2,
+ "208": -18.54999999999997,
+ "209": -72.89999999999999,
+ "210": -3.8499999999999828,
+ "211": -6.649999999999996,
+ "212": -70.29999999999983,
+ "213": -39.04999999999998,
+ "214": 29.699999999999974,
+ "215": 1.0500000000000205,
+ "216": -57.749999999999964,
+ "217": 13.900000000000034,
+ "218": -4.649999999999975,
+ "219": -93.80000000000001,
+ "220": -22.349999999999955,
+ "221": -18.749999999999968,
+ "222": 0.9500000000000042,
+ "223": -13.649999999999984,
+ "224": -68.80000000000001,
+ "225": -55.45000000000002,
+ "226": -78.44999999999996,
+ "227": -82.34999999999998,
+ "228": -3.299999999999999,
+ "229": -18.899999999999967,
+ "230": -55.400000000000084,
+ "231": 2.899999999999995,
+ "232": -12.849999999999989,
+ "233": -1.5999999999999672,
+ "234": -11.949999999999998,
+ "235": -71.2,
+ "236": -84.55000000000001,
+ "237": -5.550000000000003,
+ "238": -20.349999999999962,
+ "239": -13.849999999999985,
+ "240": -77.55000000000001,
+ "241": 51.39999999999982,
+ "242": -65.4,
+ "243": -16.650000000000016,
+ "244": -46.999999999999986,
+ "245": -77.15000000000002,
+ "246": -16.899999999999977,
+ "247": -16.449999999999978,
+ "248": 21.450000000000035,
+ "249": 9.800000000000075,
+ "250": -13.049999999999986,
+ "251": -75.64999999999996,
+ "252": -19.150000000000063,
+ "253": -24.800000000000033,
+ "254": 1.7500000000000324,
+ "255": 6.750000000000026,
+ "256": -26.70000000000004,
+ "257": 30.099999999999852,
+ "258": 20.999999999999932,
+ "259": -24.69999999999998,
+ "260": -2.799999999999984,
+ "261": 19.05000000000001,
+ "262": -14.64999999999999,
+ "263": -68.79999999999997,
+ "264": 44.499999999999964,
+ "265": -16.750000000000043,
+ "266": 13.05000000000004,
+ "267": 35.649999999999885,
+ "268": -37.50000000000007,
+ "269": -60.95,
+ "270": 54.04999999999984,
+ "271": -13.700000000000033,
+ "272": 16.500000000000018,
+ "273": -6.5999999999999925,
+ "274": -52.19999999999999,
+ "275": 23.100000000000012,
+ "276": 38.44999999999977,
+ "277": -21.69999999999997,
+ "278": 35.74999999999983,
+ "279": -21.849999999999955,
+ "280": -30.399999999999963,
+ "281": -84.70000000000002,
+ "282": 30.399999999999824,
+ "283": 23.29999999999999,
+ "284": -44.8,
+ "285": -10.149999999999988,
+ "286": -8.59999999999999,
+ "287": -5.6999999999999975,
+ "288": 12.200000000000038,
+ "289": 16.500000000000036,
+ "290": -84.75,
+ "291": 6.250000000000013,
+ "292": 14.600000000000009,
+ "293": -78.7,
+ "294": 3.34999999999998,
+ "295": -2.7999999999999874,
+ "296": 1.500000000000021,
+ "297": 27.25000000000009,
+ "298": -97.1,
+ "299": 24.399999999999974,
+ "300": 48.09999999999984,
+ "301": 42.69999999999978,
+ "302": -4.599999999999982,
+ "303": -75.9,
+ "304": -27.00000000000004,
+ "305": -1.8499999999999597,
+ "306": 2.4999999999999694,
+ "307": 0.2500000000000029,
+ "308": -10.20000000000009,
+ "309": 43.85000000000005,
+ "310": 89.00000000000013,
+ "311": 43.14999999999994,
+ "312": 1.2000000000000124,
+ "313": 91.45000000000005,
+ "314": 72.75000000000009,
+ "315": 84.49999999999991,
+ "316": -35.20000000000007,
+ "317": 11.649999999999993,
+ "318": 7.84999999999992,
+ "319": 7.6999999999999575,
+ "320": 61.150000000000105,
+ "321": -16.1,
+ "322": 106.3000000000001,
+ "323": -16.94999999999996,
+ "324": -5.499999999999972,
+ "325": -60.900000000000006,
+ "326": 28.249999999999996,
+ "327": 99.20000000000006,
+ "328": -7.050000000000033,
+ "329": 13.800000000000063,
+ "330": -7.800000000000067,
+ "331": 51.75000000000007,
+ "332": 31.000000000000032,
+ "333": 28.200000000000045,
+ "334": -89.65000000000006,
+ "335": -0.3499999999999981,
+ "336": 100.80000000000004,
+ "337": 12.799999999999972,
+ "338": 70.2,
+ "339": 35.700000000000074,
+ "340": 55.45000000000011,
+ "341": 96.95000000000003,
+ "342": 12.399999999999991,
+ "343": 50.64999999999999,
+ "344": -20.050000000000022,
+ "345": 93.95000000000005,
+ "346": 95.20000000000005,
+ "347": 13.69999999999996,
+ "348": -16.100000000000023,
+ "349": 88.35000000000004,
+ "350": 27.84999999999993,
+ "351": 34.79999999999988,
+ "352": 31.50000000000005,
+ "353": 23.69999999999996,
+ "354": 38.500000000000014,
+ "355": 35.700000000000074,
+ "356": 56.450000000000045,
+ "357": 61.8,
+ "358": 112.65000000000005,
+ "359": 46.00000000000007,
+ "360": 55.34999999999988,
+ "361": 53.099999999999966,
+ "362": 44.7000000000001,
+ "363": 36.84999999999994,
+ "364": 82.00000000000003,
+ "365": 8.149999999999975,
+ "366": -30.599999999999994,
+ "367": 40.99999999999997,
+ "368": 97.85000000000004,
+ "369": 74.15000000000012,
+ "370": 37.39999999999981,
+ "371": 63.20000000000003,
+ "372": 35.64999999999997,
+ "373": 67.20000000000007,
+ "374": -30.350000000000033,
+ "375": 28.65,
+ "376": 6.300000000000008,
+ "377": 121.00000000000001,
+ "378": 49.900000000000055,
+ "379": 64.04999999999998,
+ "380": 7.99999999999999,
+ "381": 93.7500000000001,
+ "382": 19.29999999999999,
+ "383": 85.15000000000006,
+ "384": 67.60000000000008,
+ "385": 41.05000000000002,
+ "386": 115.10000000000014,
+ "387": 62.69999999999994,
+ "388": 54.050000000000054,
+ "389": 82.65000000000008,
+ "390": 24.449999999999967,
+ "391": 68.95000000000003,
+ "392": 95.25000000000004,
+ "393": 55.54999999999988,
+ "394": 50.05000000000004,
+ "395": 7.000000000000031,
+ "396": 77.60000000000011,
+ "397": 56.84999999999996,
+ "398": 66.70000000000005,
+ "399": 73.4,
+ "400": 113.95000000000003,
+ "401": 94.95000000000006,
+ "402": 74.9,
+ "403": 101.8500000000001,
+ "404": 35.45000000000007,
+ "405": 73.00000000000006,
+ "406": 81.75000000000007,
+ "407": 92.00000000000003,
+ "408": 99.30000000000001,
+ "409": 110.80000000000001,
+ "410": 85.05000000000004,
+ "411": 25.150000000000077,
+ "412": 67.29999999999998,
+ "413": 78.15000000000002,
+ "414": 117.95,
+ "415": 59.350000000000044,
+ "416": 106.30000000000007,
+ "417": 55.14999999999997,
+ "418": 90.14999999999998,
+ "419": 79.00000000000007,
+ "420": 109.80000000000001,
+ "421": 103.20000000000005,
+ "422": 99.20000000000009,
+ "423": 74.35,
+ "424": 75.90000000000005,
+ "425": 66.80000000000004,
+ "426": 49.199999999999974,
+ "427": 17.799999999999965,
+ "428": 107.10000000000004,
+ "429": 6.299999999999996,
+ "430": 105.20000000000006,
+ "431": 65.60000000000002,
+ "432": 84.00000000000004,
+ "433": 82.34999999999995,
+ "434": 115.30000000000005,
+ "435": 106.05,
+ "436": 37.24999999999994,
+ "437": 30.649999999999913,
+ "438": 96.80000000000001,
+ "439": 119.70000000000005,
+ "440": -16.650000000000002,
+ "441": 72.75,
+ "442": 118.50000000000007,
+ "443": 65.90000000000002,
+ "444": 110.60000000000002,
+ "445": 107.55,
+ "446": 99.60000000000001,
+ "447": 94.95000000000006,
+ "448": 93.85000000000014,
+ "449": 104.00000000000007,
+ "450": 89.75000000000001,
+ "451": 85.65,
+ "452": 90.25000000000003,
+ "453": 81.5,
+ "454": 83.90000000000009,
+ "455": 98.0,
+ "456": 82.65000000000005,
+ "457": 92.94999999999995,
+ "458": 90.85000000000012,
+ "459": 102.70000000000009,
+ "460": 89.85000000000008,
+ "461": 85.00000000000011,
+ "462": 97.25000000000009,
+ "463": 111.95000000000005,
+ "464": 39.45000000000012,
+ "465": 63.49999999999998,
+ "466": 93.85000000000008,
+ "467": 114.7500000000001,
+ "468": 106.45000000000005,
+ "469": 86.85000000000002,
+ "470": 106.00000000000003,
+ "471": 102.30000000000008,
+ "472": 102.10000000000011,
+ "473": 55.049999999999976,
+ "474": 95.20000000000013,
+ "475": 101.24999999999999,
+ "476": 107.95000000000002,
+ "477": 103.25000000000006,
+ "478": 87.90000000000009,
+ "479": 51.8999999999999,
+ "480": 100.15000000000003,
+ "481": 115.00000000000007,
+ "482": 112.35000000000015,
+ "483": 109.25000000000004,
+ "484": 119.40000000000008,
+ "485": 101.45,
+ "486": 88.45000000000006,
+ "487": 104.55000000000005,
+ "488": 112.85000000000007,
+ "489": 119.05000000000017,
+ "490": 87.35000000000001,
+ "491": 100.90000000000006,
+ "492": 114.0,
+ "493": 113.00000000000004,
+ "494": 106.90000000000002,
+ "495": 107.20000000000013,
+ "496": 114.50000000000003,
+ "497": 95.44999999999982,
+ "498": 111.50000000000016,
+ "499": 106.4,
+ "500": 116.35000000000008,
+ "501": 109.65000000000006,
+ "502": 99.40000000000013,
+ "503": 106.8500000000001,
+ "504": 104.80000000000007,
+ "505": 115.70000000000002,
+ "506": 120.00000000000009,
+ "507": 113.25000000000009,
+ "508": 111.94999999999996,
+ "509": 113.55000000000011,
+ "510": 101.30000000000017,
+ "511": 113.20000000000013,
+ "512": 74.65000000000002,
+ "513": 118.45000000000012,
+ "514": 115.80000000000004,
+ "515": 119.5000000000001,
+ "516": 101.70000000000009,
+ "517": 112.45000000000005,
+ "518": 103.90000000000013,
+ "519": 114.80000000000007,
+ "520": 99.95000000000009,
+ "521": 104.65000000000006,
+ "522": 109.45000000000019,
+ "523": 111.25000000000006,
+ "524": 109.90000000000009,
+ "525": 106.80000000000013,
+ "526": 111.10000000000008,
+ "527": 112.2500000000001,
+ "528": 117.80000000000001,
+ "529": 112.35000000000016,
+ "530": 30.899999999999952,
+ "531": 111.85000000000021,
+ "532": 112.45000000000012,
+ "533": 115.1500000000001,
+ "534": 89.44999999999996,
+ "535": 106.00000000000001,
+ "536": 105.14999999999999,
+ "537": 108.54999999999994,
+ "538": 103.95000000000002,
+ "539": 109.60000000000001,
+ "540": 79.69999999999992,
+ "541": 112.5500000000002,
+ "542": 116.05000000000022,
+ "543": 118.45000000000002,
+ "544": 109.75000000000011,
+ "545": 122.25000000000011,
+ "546": 106.20000000000003,
+ "547": 111.65000000000003,
+ "548": 113.30000000000008,
+ "549": 97.0499999999999,
+ "550": 111.40000000000008,
+ "551": 112.2500000000002,
+ "552": -4.399999999999996,
+ "553": 104.30000000000005,
+ "554": 118.35000000000007,
+ "555": 115.85000000000008,
+ "556": 117.0000000000001,
+ "557": 108.59999999999994,
+ "558": 113.80000000000013,
+ "559": 103.70000000000003,
+ "560": 74.69999999999999,
+ "561": 111.50000000000011,
+ "562": 105.1500000000001,
+ "563": 102.80000000000011,
+ "564": 123.00000000000001,
+ "565": 112.45,
+ "566": 113.8000000000001,
+ "567": 102.80000000000011,
+ "568": 118.70000000000007,
+ "569": 108.59999999999997,
+ "570": 112.85000000000015,
+ "571": 113.60000000000014,
+ "572": 114.25000000000006,
+ "573": 113.30000000000013,
+ "574": 106.85000000000004,
+ "575": 111.10000000000014,
+ "576": 110.20000000000012,
+ "577": 100.64999999999998,
+ "578": 103.64999999999999,
+ "579": 114.5000000000001,
+ "580": 113.35000000000005,
+ "581": 114.75000000000007,
+ "582": 110.10000000000015,
+ "583": 101.44999999999993,
+ "584": 113.90000000000018,
+ "585": 113.00000000000001,
+ "586": 106.50000000000009,
+ "587": 104.89999999999999,
+ "588": 114.90000000000012,
+ "589": 112.90000000000005,
+ "590": 119.75000000000003,
+ "591": 101.39999999999998,
+ "592": 103.75000000000003,
+ "593": 101.84999999999988,
+ "594": 109.65000000000003,
+ "595": 108.20000000000006,
+ "596": 115.5000000000002,
+ "597": 116.15000000000009,
+ "598": 107.30000000000007,
+ "599": 109.85,
+ "600": 109.25000000000013,
+ "601": 104.45000000000003,
+ "602": 111.95000000000005,
+ "603": 116.55000000000013,
+ "604": 114.80000000000001,
+ "605": 116.05000000000005,
+ "606": 114.90000000000008,
+ "607": 113.45000000000014,
+ "608": 109.60000000000015,
+ "609": 111.55000000000004,
+ "610": 112.00000000000018,
+ "611": 108.05000000000018,
+ "612": 115.30000000000004,
+ "613": 112.60000000000012,
+ "614": 114.65000000000005,
+ "615": 111.95000000000012,
+ "616": 113.05000000000024,
+ "617": 116.35000000000011,
+ "618": 110.95000000000002,
+ "619": 109.25000000000006,
+ "620": 113.90000000000025,
+ "621": 100.44999999999987,
+ "622": 109.15000000000018,
+ "623": 116.9000000000001,
+ "624": 115.10000000000014,
+ "625": 113.25000000000016,
+ "626": 110.3500000000001,
+ "627": 113.85000000000005,
+ "628": 108.75000000000013,
+ "629": 108.85000000000004,
+ "630": 106.20000000000016,
+ "631": 108.55000000000011,
+ "632": 114.8500000000001,
+ "633": 104.50000000000003,
+ "634": 108.84999999999988,
+ "635": 109.10000000000004,
+ "636": 120.79999999999998,
+ "637": 112.7,
+ "638": 106.40000000000012,
+ "639": 113.80000000000014,
+ "640": 3.850000000000038,
+ "641": 59.649999999999864,
+ "642": 60.19999999999987,
+ "643": 42.59999999999997,
+ "644": 36.04999999999996,
+ "645": 109.25000000000011,
+ "646": 52.25,
+ "647": 119.15000000000005,
+ "648": 56.89999999999993,
+ "649": 96.19999999999982,
+ "650": 73.55000000000005,
+ "651": 54.94999999999993,
+ "652": 55.04999999999993,
+ "653": 45.0999999999999,
+ "654": 112.20000000000005,
+ "655": 55.39999999999993,
+ "656": 111.0,
+ "657": 65.39999999999996,
+ "658": 66.44999999999987,
+ "659": 54.54999999999988,
+ "660": 65.29999999999984,
+ "661": 61.099999999999866,
+ "662": 109.54999999999997,
+ "663": 14.550000000000002,
+ "664": 85.30000000000001,
+ "665": 86.89999999999999,
+ "666": 74.34999999999997,
+ "667": 18.399999999999963,
+ "668": 112.10000000000014,
+ "669": 107.40000000000003,
+ "670": 111.70000000000012,
+ "671": 61.64999999999984,
+ "672": 97.85000000000002,
+ "673": 16.649999999999892,
+ "674": 101.75000000000009,
+ "675": 111.85000000000005,
+ "676": 106.90000000000013,
+ "677": 108.85000000000004,
+ "678": 108.5,
+ "679": 111.70000000000019,
+ "680": 108.95000000000016,
+ "681": 111.3500000000002,
+ "682": 60.69999999999979,
+ "683": 74.94999999999993,
+ "684": 60.64999999999983,
+ "685": 112.69999999999997,
+ "686": 81.40000000000003,
+ "687": 113.60000000000018,
+ "688": 77.80000000000004,
+ "689": 107.69999999999997,
+ "690": 111.30000000000014,
+ "691": -0.9999999999999816,
+ "692": 87.90000000000019,
+ "693": 110.35,
+ "694": 113.69999999999997,
+ "695": 7.600000000000028,
+ "696": 114.8,
+ "697": 69.84999999999998,
+ "698": 27.349999999999895,
+ "699": 115.5000000000001,
+ "700": 109.45000000000009,
+ "701": 18.849999999999987,
+ "702": 67.84999999999998,
+ "703": 108.65,
+ "704": 110.25000000000017,
+ "705": 112.6000000000001,
+ "706": 116.05000000000008,
+ "707": 117.25000000000003,
+ "708": 110.80000000000017,
+ "709": 93.60000000000001,
+ "710": 109.9000000000001,
+ "711": 118.25000000000004,
+ "712": 112.95000000000019,
+ "713": 106.3000000000001,
+ "714": 109.90000000000013,
+ "715": 99.90000000000003,
+ "716": 117.65000000000018,
+ "717": 104.05,
+ "718": 66.34999999999982,
+ "719": 100.44999999999999,
+ "720": 102.25000000000013,
+ "721": 111.65000000000008,
+ "722": 108.94999999999997,
+ "723": 49.64999999999995,
+ "724": 102.20000000000006,
+ "725": 109.75000000000004,
+ "726": 102.70000000000005,
+ "727": 38.5999999999999,
+ "728": 95.50000000000009,
+ "729": 118.30000000000004,
+ "730": 112.30000000000017,
+ "731": 108.25000000000009,
+ "732": 107.70000000000019,
+ "733": 110.44999999999992,
+ "734": 118.8000000000001,
+ "735": 105.45000000000003,
+ "736": 37.599999999999916,
+ "737": 18.74999999999995,
+ "738": 102.34999999999984,
+ "739": 107.50000000000016,
+ "740": 105.45000000000007,
+ "741": 96.49999999999999,
+ "742": 16.300000000000036,
+ "743": 32.15000000000006,
+ "744": 112.10000000000008,
+ "745": 76.80000000000008,
+ "746": 108.4000000000001,
+ "747": 113.10000000000001,
+ "748": 77.64999999999993,
+ "749": 75.54999999999997,
+ "750": 115.35000000000016,
+ "751": 101.85000000000007,
+ "752": 118.85000000000004,
+ "753": -33.04999999999998,
+ "754": 81.85000000000021,
+ "755": 111.60000000000015,
+ "756": 113.10000000000008,
+ "757": 103.7,
+ "758": 107.39999999999998,
+ "759": 114.20000000000005,
+ "760": 107.9,
+ "761": 115.15000000000013,
+ "762": 110.20000000000016,
+ "763": 117.25000000000001,
+ "764": 59.74999999999984,
+ "765": 115.50000000000001,
+ "766": 105.75,
+ "767": 114.80000000000022,
+ "768": 111.75000000000003,
+ "769": 119.0,
+ "770": 114.15000000000009,
+ "771": 117.05000000000008,
+ "772": 113.95000000000006,
+ "773": 112.15000000000008,
+ "774": 106.15,
+ "775": 104.35000000000004,
+ "776": 111.75000000000007,
+ "777": 109.70000000000005,
+ "778": 109.95000000000006,
+ "779": 123.60000000000012,
+ "780": 108.85000000000005,
+ "781": 111.30000000000005,
+ "782": 107.85000000000008,
+ "783": 111.25000000000023,
+ "784": 109.75,
+ "785": 113.60000000000008,
+ "786": 104.75000000000004,
+ "787": 116.25000000000014,
+ "788": 104.55,
+ "789": 108.95000000000007,
+ "790": 112.75000000000023,
+ "791": 97.80000000000005,
+ "792": 113.1000000000001,
+ "793": 110.60000000000012,
+ "794": 111.15,
+ "795": 118.39999999999995,
+ "796": 111.24999999999999,
+ "797": 112.35000000000008,
+ "798": 108.60000000000014,
+ "799": 0.2500000000000129,
+ "800": 108.40000000000002,
+ "801": 114.0,
+ "802": 119.05000000000007,
+ "803": 114.25000000000014,
+ "804": 117.10000000000008,
+ "805": 112.35000000000001,
+ "806": 109.99999999999996,
+ "807": 107.55,
+ "808": 113.70000000000009,
+ "809": 114.05000000000005,
+ "810": 107.30000000000001,
+ "811": 117.30000000000008,
+ "812": 116.80000000000007,
+ "813": 110.6,
+ "814": 109.34999999999997,
+ "815": 104.45000000000002,
+ "816": 105.70000000000016,
+ "817": 110.85000000000005,
+ "818": 119.70000000000019,
+ "819": 106.49999999999997,
+ "820": 110.29999999999988,
+ "821": 110.2000000000001,
+ "822": 114.05000000000004,
+ "823": 114.05000000000008,
+ "824": 109.8000000000001,
+ "825": 120.05000000000008,
+ "826": 116.80000000000004,
+ "827": 108.95000000000006,
+ "828": 101.20000000000007,
+ "829": 115.10000000000008,
+ "830": 117.30000000000001,
+ "831": 114.50000000000009,
+ "832": 114.15000000000013,
+ "833": 112.6000000000001,
+ "834": 119.30000000000008,
+ "835": 114.45000000000013,
+ "836": 114.55000000000015,
+ "837": 102.85000000000004,
+ "838": 108.25000000000004,
+ "839": 121.45000000000006,
+ "840": 110.30000000000007,
+ "841": 111.55000000000004,
+ "842": 111.10000000000014,
+ "843": 112.85000000000007,
+ "844": 108.90000000000015,
+ "845": 117.20000000000006,
+ "846": 112.45000000000013,
+ "847": 70.74999999999996,
+ "848": 114.79999999999993,
+ "849": 119.75000000000018,
+ "850": 43.749999999999865,
+ "851": 110.49999999999997,
+ "852": 73.54999999999998,
+ "853": 108.69999999999995,
+ "854": 108.8000000000001,
+ "855": 107.25000000000011,
+ "856": 108.55000000000011,
+ "857": 118.55000000000005,
+ "858": 109.15000000000003,
+ "859": 53.69999999999987,
+ "860": 121.35000000000014,
+ "861": 105.45000000000013,
+ "862": 114.80000000000015,
+ "863": 110.4,
+ "864": 107.14999999999999,
+ "865": 118.55000000000017,
+ "866": 78.80000000000003,
+ "867": 115.55000000000013,
+ "868": 91.5000000000002,
+ "869": 42.150000000000006,
+ "870": 117.7,
+ "871": 69.59999999999997,
+ "872": 93.50000000000003,
+ "873": 111.15000000000009,
+ "874": 108.95000000000009,
+ "875": 97.55000000000007,
+ "876": 86.85000000000004,
+ "877": 102.10000000000011,
+ "878": 109.10000000000008,
+ "879": 114.90000000000015,
+ "880": 113.05000000000007,
+ "881": 108.44999999999996,
+ "882": 108.10000000000005,
+ "883": 110.35000000000005,
+ "884": 46.84999999999993,
+ "885": 104.35000000000014,
+ "886": 105.89999999999999,
+ "887": 99.00000000000001,
+ "888": 103.15000000000006,
+ "889": 111.10000000000002,
+ "890": 76.09999999999997,
+ "891": 109.60000000000025,
+ "892": 112.45000000000012,
+ "893": 109.74999999999993,
+ "894": 102.10000000000008,
+ "895": 107.80000000000021,
+ "896": 98.05000000000003,
+ "897": 42.249999999999964,
+ "898": 100.85000000000004,
+ "899": 113.14999999999993,
+ "900": 80.39999999999993,
+ "901": 107.00000000000013,
+ "902": 45.39999999999989,
+ "903": 118.95000000000007,
+ "904": 107.65000000000008,
+ "905": 106.09999999999995,
+ "906": 99.2500000000001,
+ "907": 116.00000000000013,
+ "908": 115.60000000000018,
+ "909": 91.60000000000015,
+ "910": 94.74999999999997,
+ "911": 105.40000000000013,
+ "912": 110.30000000000011,
+ "913": 108.05000000000008,
+ "914": -10.999999999999993,
+ "915": 104.20000000000013,
+ "916": 115.19999999999999,
+ "917": 114.70000000000007,
+ "918": 118.50000000000017,
+ "919": 108.25000000000001,
+ "920": 105.95000000000012,
+ "921": 113.60000000000004,
+ "922": 108.85000000000005,
+ "923": 106.19999999999997,
+ "924": 97.80000000000004,
+ "925": 104.40000000000013,
+ "926": 115.6500000000002,
+ "927": 107.50000000000007,
+ "928": 111.39999999999998,
+ "929": 112.70000000000014,
+ "930": 112.1000000000001,
+ "931": 109.10000000000002,
+ "932": 102.85000000000004,
+ "933": 110.90000000000016,
+ "934": 69.49999999999983,
+ "935": 114.7000000000001,
+ "936": 111.00000000000006,
+ "937": 114.5500000000002,
+ "938": 113.7500000000001,
+ "939": 109.00000000000007,
+ "940": 61.79999999999981,
+ "941": 115.4000000000002,
+ "942": 106.10000000000008,
+ "943": 115.95000000000002,
+ "944": 81.60000000000007,
+ "945": 115.4500000000001,
+ "946": 108.80000000000008,
+ "947": 112.95000000000012,
+ "948": 109.95000000000003,
+ "949": 111.5000000000002,
+ "950": 103.6,
+ "951": 95.90000000000002,
+ "952": 110.59999999999997,
+ "953": 108.19999999999999,
+ "954": 113.45000000000017,
+ "955": 102.95000000000006,
+ "956": 107.50000000000003,
+ "957": 85.89999999999999,
+ "958": 113.70000000000016,
+ "959": 117.30000000000001,
+ "960": 107.20000000000003,
+ "961": 106.3000000000001,
+ "962": 96.94999999999995,
+ "963": 113.30000000000015,
+ "964": 105.60000000000005,
+ "965": 112.60000000000005,
+ "966": 112.75000000000003,
+ "967": 79.54999999999997,
+ "968": 113.0000000000001,
+ "969": 106.3000000000001,
+ "970": 112.84999999999991,
+ "971": 97.45000000000003,
+ "972": 117.20000000000009,
+ "973": 111.40000000000023,
+ "974": 111.35000000000005,
+ "975": 121.6000000000001,
+ "976": 106.94999999999996,
+ "977": 111.70000000000016,
+ "978": 107.70000000000003,
+ "979": 107.40000000000019,
+ "980": 104.4499999999999,
+ "981": 116.25000000000013,
+ "982": 110.94999999999999,
+ "983": 114.55000000000005,
+ "984": 38.399999999999785,
+ "985": 111.45000000000007,
+ "986": 96.15000000000006,
+ "987": 108.70000000000003,
+ "988": 110.75000000000007,
+ "989": 110.05000000000011,
+ "990": 116.05000000000005,
+ "991": 103.70000000000003,
+ "992": 115.60000000000015,
+ "993": 114.15000000000005,
+ "994": 4.15,
+ "995": 111.35000000000008,
+ "996": 115.90000000000005,
+ "997": 111.70000000000009,
+ "998": 108.04999999999988,
+ "999": 105.25000000000017,
+ "1000": 108.95000000000016
+ }
+ },
+ "config": {
+ "io_settings": {
+ "save_agent_actions": true,
+ "save_step_metadata": false,
+ "save_pcap_logs": false,
+ "save_sys_logs": false,
+ "sys_log_level": "WARNING"
+ },
+ "game": {
+ "max_episode_length": 128,
+ "ports": [
+ "HTTP",
+ "POSTGRES_SERVER"
+ ],
+ "protocols": [
+ "ICMP",
+ "TCP",
+ "UDP"
+ ],
+ "thresholds": {
+ "nmne": {
+ "high": 10,
+ "medium": 5,
+ "low": 0
+ }
+ }
+ },
+ "agents": [
+ {
+ "ref": "client_2_green_user",
+ "team": "GREEN",
+ "type": "ProbabilisticAgent",
+ "agent_settings": {
+ "action_probabilities": {
+ "0": 0.3,
+ "1": 0.6,
+ "2": 0.1
+ }
+ },
+ "observation_space": null,
+ "action_space": {
+ "action_list": [
+ {
+ "type": "DONOTHING"
+ },
+ {
+ "type": "NODE_APPLICATION_EXECUTE"
+ }
+ ],
+ "options": {
+ "nodes": [
+ {
+ "node_name": "client_2",
+ "applications": [
+ {
+ "application_name": "WebBrowser"
+ },
+ {
+ "application_name": "DatabaseClient"
+ }
+ ]
+ }
+ ],
+ "max_folders_per_node": 1,
+ "max_files_per_folder": 1,
+ "max_services_per_node": 1,
+ "max_applications_per_node": 2
+ },
+ "action_map": {
+ "0": {
+ "action": "DONOTHING",
+ "options": {}
+ },
+ "1": {
+ "action": "NODE_APPLICATION_EXECUTE",
+ "options": {
+ "node_id": 0,
+ "application_id": 0
+ }
+ },
+ "2": {
+ "action": "NODE_APPLICATION_EXECUTE",
+ "options": {
+ "node_id": 0,
+ "application_id": 1
+ }
+ }
+ }
+ },
+ "reward_function": {
+ "reward_components": [
+ {
+ "type": "WEBPAGE_UNAVAILABLE_PENALTY",
+ "weight": 0.25,
+ "options": {
+ "node_hostname": "client_2"
+ }
+ },
+ {
+ "type": "GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY",
+ "weight": 0.05,
+ "options": {
+ "node_hostname": "client_2"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "ref": "client_1_green_user",
+ "team": "GREEN",
+ "type": "ProbabilisticAgent",
+ "agent_settings": {
+ "action_probabilities": {
+ "0": 0.3,
+ "1": 0.6,
+ "2": 0.1
+ }
+ },
+ "observation_space": null,
+ "action_space": {
+ "action_list": [
+ {
+ "type": "DONOTHING"
+ },
+ {
+ "type": "NODE_APPLICATION_EXECUTE"
+ }
+ ],
+ "options": {
+ "nodes": [
+ {
+ "node_name": "client_1",
+ "applications": [
+ {
+ "application_name": "WebBrowser"
+ },
+ {
+ "application_name": "DatabaseClient"
+ }
+ ]
+ }
+ ],
+ "max_folders_per_node": 1,
+ "max_files_per_folder": 1,
+ "max_services_per_node": 1,
+ "max_applications_per_node": 2
+ },
+ "action_map": {
+ "0": {
+ "action": "DONOTHING",
+ "options": {}
+ },
+ "1": {
+ "action": "NODE_APPLICATION_EXECUTE",
+ "options": {
+ "node_id": 0,
+ "application_id": 0
+ }
+ },
+ "2": {
+ "action": "NODE_APPLICATION_EXECUTE",
+ "options": {
+ "node_id": 0,
+ "application_id": 1
+ }
+ }
+ }
+ },
+ "reward_function": {
+ "reward_components": [
+ {
+ "type": "WEBPAGE_UNAVAILABLE_PENALTY",
+ "weight": 0.25,
+ "options": {
+ "node_hostname": "client_1"
+ }
+ },
+ {
+ "type": "GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY",
+ "weight": 0.05,
+ "options": {
+ "node_hostname": "client_1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "ref": "data_manipulation_attacker",
+ "team": "RED",
+ "type": "RedDatabaseCorruptingAgent",
+ "observation_space": null,
+ "action_space": {
+ "action_list": [
+ {
+ "type": "DONOTHING"
+ },
+ {
+ "type": "NODE_APPLICATION_EXECUTE"
+ }
+ ],
+ "options": {
+ "nodes": [
+ {
+ "node_name": "client_1",
+ "applications": [
+ {
+ "application_name": "DataManipulationBot"
+ }
+ ]
+ },
+ {
+ "node_name": "client_2",
+ "applications": [
+ {
+ "application_name": "DataManipulationBot"
+ }
+ ]
+ }
+ ],
+ "max_folders_per_node": 1,
+ "max_files_per_folder": 1,
+ "max_services_per_node": 1
+ }
+ },
+ "reward_function": {
+ "reward_components": [
+ {
+ "type": "DUMMY"
+ }
+ ]
+ },
+ "agent_settings": {
+ "start_settings": {
+ "start_step": 25,
+ "frequency": 20,
+ "variance": 5
+ }
+ }
+ },
+ {
+ "ref": "defender",
+ "team": "BLUE",
+ "type": "ProxyAgent",
+ "observation_space": {
+ "type": "CUSTOM",
+ "options": {
+ "components": [
+ {
+ "type": "NODES",
+ "label": "NODES",
+ "options": {
+ "hosts": [
+ {
+ "hostname": "domain_controller"
+ },
+ {
+ "hostname": "web_server",
+ "services": [
+ {
+ "service_name": "WebServer"
+ }
+ ]
+ },
+ {
+ "hostname": "database_server",
+ "folders": [
+ {
+ "folder_name": "database",
+ "files": [
+ {
+ "file_name": "database.db"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "hostname": "backup_server"
+ },
+ {
+ "hostname": "security_suite"
+ },
+ {
+ "hostname": "client_1"
+ },
+ {
+ "hostname": "client_2"
+ }
+ ],
+ "num_services": 1,
+ "num_applications": 0,
+ "num_folders": 1,
+ "num_files": 1,
+ "num_nics": 2,
+ "include_num_access": false,
+ "include_nmne": true,
+ "routers": [
+ {
+ "hostname": "router_1"
+ }
+ ],
+ "num_ports": 0,
+ "ip_list": [
+ "192.168.1.10",
+ "192.168.1.12",
+ "192.168.1.14",
+ "192.168.1.16",
+ "192.168.1.110",
+ "192.168.10.21",
+ "192.168.10.22",
+ "192.168.10.110"
+ ],
+ "wildcard_list": [
+ "0.0.0.1"
+ ],
+ "port_list": [
+ 80,
+ 5432
+ ],
+ "protocol_list": [
+ "ICMP",
+ "TCP",
+ "UDP"
+ ],
+ "num_rules": 10
+ }
+ },
+ {
+ "type": "LINKS",
+ "label": "LINKS",
+ "options": {
+ "link_references": [
+ "router_1:eth-1<->switch_1:eth-8",
+ "router_1:eth-2<->switch_2:eth-8",
+ "switch_1:eth-1<->domain_controller:eth-1",
+ "switch_1:eth-2<->web_server:eth-1",
+ "switch_1:eth-3<->database_server:eth-1",
+ "switch_1:eth-4<->backup_server:eth-1",
+ "switch_1:eth-7<->security_suite:eth-1",
+ "switch_2:eth-1<->client_1:eth-1",
+ "switch_2:eth-2<->client_2:eth-1",
+ "switch_2:eth-7<->security_suite:eth-2"
+ ]
+ }
+ },
+ {
+ "type": "NONE",
+ "label": "ICS",
+ "options": {}
+ }
+ ]
+ }
+ },
+ "action_space": {
+ "action_list": [
+ {
+ "type": "DONOTHING"
+ },
+ {
+ "type": "NODE_SERVICE_SCAN"
+ },
+ {
+ "type": "NODE_SERVICE_STOP"
+ },
+ {
+ "type": "NODE_SERVICE_START"
+ },
+ {
+ "type": "NODE_SERVICE_PAUSE"
+ },
+ {
+ "type": "NODE_SERVICE_RESUME"
+ },
+ {
+ "type": "NODE_SERVICE_RESTART"
+ },
+ {
+ "type": "NODE_SERVICE_DISABLE"
+ },
+ {
+ "type": "NODE_SERVICE_ENABLE"
+ },
+ {
+ "type": "NODE_SERVICE_FIX"
+ },
+ {
+ "type": "NODE_FILE_SCAN"
+ },
+ {
+ "type": "NODE_FILE_CHECKHASH"
+ },
+ {
+ "type": "NODE_FILE_DELETE"
+ },
+ {
+ "type": "NODE_FILE_REPAIR"
+ },
+ {
+ "type": "NODE_FILE_RESTORE"
+ },
+ {
+ "type": "NODE_FOLDER_SCAN"
+ },
+ {
+ "type": "NODE_FOLDER_CHECKHASH"
+ },
+ {
+ "type": "NODE_FOLDER_REPAIR"
+ },
+ {
+ "type": "NODE_FOLDER_RESTORE"
+ },
+ {
+ "type": "NODE_OS_SCAN"
+ },
+ {
+ "type": "NODE_SHUTDOWN"
+ },
+ {
+ "type": "NODE_STARTUP"
+ },
+ {
+ "type": "NODE_RESET"
+ },
+ {
+ "type": "ROUTER_ACL_ADDRULE"
+ },
+ {
+ "type": "ROUTER_ACL_REMOVERULE"
+ },
+ {
+ "type": "HOST_NIC_ENABLE"
+ },
+ {
+ "type": "HOST_NIC_DISABLE"
+ }
+ ],
+ "action_map": {
+ "0": {
+ "action": "DONOTHING",
+ "options": {}
+ },
+ "1": {
+ "action": "NODE_SERVICE_SCAN",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "2": {
+ "action": "NODE_SERVICE_STOP",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "3": {
+ "action": "NODE_SERVICE_START",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "4": {
+ "action": "NODE_SERVICE_PAUSE",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "5": {
+ "action": "NODE_SERVICE_RESUME",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "6": {
+ "action": "NODE_SERVICE_RESTART",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "7": {
+ "action": "NODE_SERVICE_DISABLE",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "8": {
+ "action": "NODE_SERVICE_ENABLE",
+ "options": {
+ "node_id": 1,
+ "service_id": 0
+ }
+ },
+ "9": {
+ "action": "NODE_FILE_SCAN",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0,
+ "file_id": 0
+ }
+ },
+ "10": {
+ "action": "NODE_FILE_CHECKHASH",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0,
+ "file_id": 0
+ }
+ },
+ "11": {
+ "action": "NODE_FILE_DELETE",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0,
+ "file_id": 0
+ }
+ },
+ "12": {
+ "action": "NODE_FILE_REPAIR",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0,
+ "file_id": 0
+ }
+ },
+ "13": {
+ "action": "NODE_SERVICE_FIX",
+ "options": {
+ "node_id": 2,
+ "service_id": 0
+ }
+ },
+ "14": {
+ "action": "NODE_FOLDER_SCAN",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0
+ }
+ },
+ "15": {
+ "action": "NODE_FOLDER_CHECKHASH",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0
+ }
+ },
+ "16": {
+ "action": "NODE_FOLDER_REPAIR",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0
+ }
+ },
+ "17": {
+ "action": "NODE_FOLDER_RESTORE",
+ "options": {
+ "node_id": 2,
+ "folder_id": 0
+ }
+ },
+ "18": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 0
+ }
+ },
+ "19": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 0
+ }
+ },
+ "20": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 0
+ }
+ },
+ "21": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 0
+ }
+ },
+ "22": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 1
+ }
+ },
+ "23": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 1
+ }
+ },
+ "24": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 1
+ }
+ },
+ "25": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 1
+ }
+ },
+ "26": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 2
+ }
+ },
+ "27": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 2
+ }
+ },
+ "28": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 2
+ }
+ },
+ "29": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 2
+ }
+ },
+ "30": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 3
+ }
+ },
+ "31": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 3
+ }
+ },
+ "32": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 3
+ }
+ },
+ "33": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 3
+ }
+ },
+ "34": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 4
+ }
+ },
+ "35": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 4
+ }
+ },
+ "36": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 4
+ }
+ },
+ "37": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 4
+ }
+ },
+ "38": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 5
+ }
+ },
+ "39": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 5
+ }
+ },
+ "40": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 5
+ }
+ },
+ "41": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 5
+ }
+ },
+ "42": {
+ "action": "NODE_OS_SCAN",
+ "options": {
+ "node_id": 6
+ }
+ },
+ "43": {
+ "action": "NODE_SHUTDOWN",
+ "options": {
+ "node_id": 6
+ }
+ },
+ "44": {
+ "action": "NODE_STARTUP",
+ "options": {
+ "node_id": 6
+ }
+ },
+ "45": {
+ "action": "NODE_RESET",
+ "options": {
+ "node_id": 6
+ }
+ },
+ "46": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 1,
+ "permission": 2,
+ "source_ip_id": 7,
+ "dest_ip_id": 1,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 1,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "47": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 2,
+ "permission": 2,
+ "source_ip_id": 8,
+ "dest_ip_id": 1,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 1,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "48": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 3,
+ "permission": 2,
+ "source_ip_id": 7,
+ "dest_ip_id": 3,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 3,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "49": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 4,
+ "permission": 2,
+ "source_ip_id": 8,
+ "dest_ip_id": 3,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 3,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "50": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 5,
+ "permission": 2,
+ "source_ip_id": 7,
+ "dest_ip_id": 4,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 3,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "51": {
+ "action": "ROUTER_ACL_ADDRULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 6,
+ "permission": 2,
+ "source_ip_id": 8,
+ "dest_ip_id": 4,
+ "source_port_id": 1,
+ "dest_port_id": 1,
+ "protocol_id": 3,
+ "source_wildcard_id": 0,
+ "dest_wildcard_id": 0
+ }
+ },
+ "52": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 0
+ }
+ },
+ "53": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 1
+ }
+ },
+ "54": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 2
+ }
+ },
+ "55": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 3
+ }
+ },
+ "56": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 4
+ }
+ },
+ "57": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 5
+ }
+ },
+ "58": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 6
+ }
+ },
+ "59": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 7
+ }
+ },
+ "60": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 8
+ }
+ },
+ "61": {
+ "action": "ROUTER_ACL_REMOVERULE",
+ "options": {
+ "target_router_nodename": "router_1",
+ "position": 9
+ }
+ },
+ "62": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 0,
+ "nic_id": 0
+ }
+ },
+ "63": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 0,
+ "nic_id": 0
+ }
+ },
+ "64": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 1,
+ "nic_id": 0
+ }
+ },
+ "65": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 1,
+ "nic_id": 0
+ }
+ },
+ "66": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 2,
+ "nic_id": 0
+ }
+ },
+ "67": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 2,
+ "nic_id": 0
+ }
+ },
+ "68": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 3,
+ "nic_id": 0
+ }
+ },
+ "69": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 3,
+ "nic_id": 0
+ }
+ },
+ "70": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 4,
+ "nic_id": 0
+ }
+ },
+ "71": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 4,
+ "nic_id": 0
+ }
+ },
+ "72": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 4,
+ "nic_id": 1
+ }
+ },
+ "73": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 4,
+ "nic_id": 1
+ }
+ },
+ "74": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 5,
+ "nic_id": 0
+ }
+ },
+ "75": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 5,
+ "nic_id": 0
+ }
+ },
+ "76": {
+ "action": "HOST_NIC_DISABLE",
+ "options": {
+ "node_id": 6,
+ "nic_id": 0
+ }
+ },
+ "77": {
+ "action": "HOST_NIC_ENABLE",
+ "options": {
+ "node_id": 6,
+ "nic_id": 0
+ }
+ }
+ },
+ "options": {
+ "nodes": [
+ {
+ "node_name": "domain_controller"
+ },
+ {
+ "node_name": "web_server",
+ "applications": [
+ {
+ "application_name": "DatabaseClient"
+ }
+ ],
+ "services": [
+ {
+ "service_name": "WebServer"
+ }
+ ]
+ },
+ {
+ "node_name": "database_server",
+ "folders": [
+ {
+ "folder_name": "database",
+ "files": [
+ {
+ "file_name": "database.db"
+ }
+ ]
+ }
+ ],
+ "services": [
+ {
+ "service_name": "DatabaseService"
+ }
+ ]
+ },
+ {
+ "node_name": "backup_server"
+ },
+ {
+ "node_name": "security_suite"
+ },
+ {
+ "node_name": "client_1"
+ },
+ {
+ "node_name": "client_2"
+ }
+ ],
+ "max_folders_per_node": 2,
+ "max_files_per_folder": 2,
+ "max_services_per_node": 2,
+ "max_nics_per_node": 8,
+ "max_acl_rules": 10,
+ "ip_list": [
+ "192.168.1.10",
+ "192.168.1.12",
+ "192.168.1.14",
+ "192.168.1.16",
+ "192.168.1.110",
+ "192.168.10.21",
+ "192.168.10.22",
+ "192.168.10.110"
+ ]
+ }
+ },
+ "reward_function": {
+ "reward_components": [
+ {
+ "type": "DATABASE_FILE_INTEGRITY",
+ "weight": 0.4,
+ "options": {
+ "node_hostname": "database_server",
+ "folder_name": "database",
+ "file_name": "database.db"
+ }
+ },
+ {
+ "type": "SHARED_REWARD",
+ "weight": 1.0,
+ "options": {
+ "agent_name": "client_1_green_user"
+ }
+ },
+ {
+ "type": "SHARED_REWARD",
+ "weight": 1.0,
+ "options": {
+ "agent_name": "client_2_green_user"
+ }
+ }
+ ]
+ },
+ "agent_settings": {
+ "flatten_obs": true
+ }
+ }
+ ],
+ "simulation": {
+ "network": {
+ "nmne_config": {
+ "capture_nmne": true,
+ "nmne_capture_keywords": [
+ "DELETE"
+ ]
+ },
+ "nodes": [
+ {
+ "hostname": "router_1",
+ "type": "router",
+ "num_ports": 5,
+ "ports": {
+ "1": {
+ "ip_address": "192.168.1.1",
+ "subnet_mask": "255.255.255.0"
+ },
+ "2": {
+ "ip_address": "192.168.10.1",
+ "subnet_mask": "255.255.255.0"
+ }
+ },
+ "acl": {
+ "18": {
+ "action": "PERMIT",
+ "src_port": "POSTGRES_SERVER",
+ "dst_port": "POSTGRES_SERVER"
+ },
+ "19": {
+ "action": "PERMIT",
+ "src_port": "DNS",
+ "dst_port": "DNS"
+ },
+ "20": {
+ "action": "PERMIT",
+ "src_port": "FTP",
+ "dst_port": "FTP"
+ },
+ "21": {
+ "action": "PERMIT",
+ "src_port": "HTTP",
+ "dst_port": "HTTP"
+ },
+ "22": {
+ "action": "PERMIT",
+ "src_port": "ARP",
+ "dst_port": "ARP"
+ },
+ "23": {
+ "action": "PERMIT",
+ "protocol": "ICMP"
+ }
+ }
+ },
+ {
+ "hostname": "switch_1",
+ "type": "switch",
+ "num_ports": 8
+ },
+ {
+ "hostname": "switch_2",
+ "type": "switch",
+ "num_ports": 8
+ },
+ {
+ "hostname": "domain_controller",
+ "type": "server",
+ "ip_address": "192.168.1.10",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.1.1",
+ "services": [
+ {
+ "type": "DNSServer",
+ "options": {
+ "domain_mapping": {
+ "arcd.com": "192.168.1.12"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "hostname": "web_server",
+ "type": "server",
+ "ip_address": "192.168.1.12",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.1.1",
+ "dns_server": "192.168.1.10",
+ "services": [
+ {
+ "type": "WebServer"
+ }
+ ],
+ "applications": [
+ {
+ "type": "DatabaseClient",
+ "options": {
+ "db_server_ip": "192.168.1.14"
+ }
+ }
+ ]
+ },
+ {
+ "hostname": "database_server",
+ "type": "server",
+ "ip_address": "192.168.1.14",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.1.1",
+ "dns_server": "192.168.1.10",
+ "services": [
+ {
+ "type": "DatabaseService",
+ "options": {
+ "backup_server_ip": "192.168.1.16"
+ }
+ },
+ {
+ "type": "FTPClient"
+ }
+ ]
+ },
+ {
+ "hostname": "backup_server",
+ "type": "server",
+ "ip_address": "192.168.1.16",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.1.1",
+ "dns_server": "192.168.1.10",
+ "services": [
+ {
+ "type": "FTPServer"
+ }
+ ]
+ },
+ {
+ "hostname": "security_suite",
+ "type": "server",
+ "ip_address": "192.168.1.110",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.1.1",
+ "dns_server": "192.168.1.10",
+ "network_interfaces": {
+ "2": {
+ "ip_address": "192.168.10.110",
+ "subnet_mask": "255.255.255.0"
+ }
+ }
+ },
+ {
+ "hostname": "client_1",
+ "type": "computer",
+ "ip_address": "192.168.10.21",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.10.1",
+ "dns_server": "192.168.1.10",
+ "applications": [
+ {
+ "type": "DataManipulationBot",
+ "options": {
+ "port_scan_p_of_success": 0.8,
+ "data_manipulation_p_of_success": 0.8,
+ "payload": "DELETE",
+ "server_ip": "192.168.1.14"
+ }
+ },
+ {
+ "type": "WebBrowser",
+ "options": {
+ "target_url": "http://arcd.com/users/"
+ }
+ },
+ {
+ "type": "DatabaseClient",
+ "options": {
+ "db_server_ip": "192.168.1.14"
+ }
+ }
+ ],
+ "services": [
+ {
+ "type": "DNSClient"
+ }
+ ]
+ },
+ {
+ "hostname": "client_2",
+ "type": "computer",
+ "ip_address": "192.168.10.22",
+ "subnet_mask": "255.255.255.0",
+ "default_gateway": "192.168.10.1",
+ "dns_server": "192.168.1.10",
+ "applications": [
+ {
+ "type": "WebBrowser",
+ "options": {
+ "target_url": "http://arcd.com/users/"
+ }
+ },
+ {
+ "type": "DataManipulationBot",
+ "options": {
+ "port_scan_p_of_success": 0.8,
+ "data_manipulation_p_of_success": 0.8,
+ "payload": "DELETE",
+ "server_ip": "192.168.1.14"
+ }
+ },
+ {
+ "type": "DatabaseClient",
+ "options": {
+ "db_server_ip": "192.168.1.14"
+ }
+ }
+ ],
+ "services": [
+ {
+ "type": "DNSClient"
+ }
+ ]
+ }
+ ],
+ "links": [
+ {
+ "endpoint_a_hostname": "router_1",
+ "endpoint_a_port": 1,
+ "endpoint_b_hostname": "switch_1",
+ "endpoint_b_port": 8
+ },
+ {
+ "endpoint_a_hostname": "router_1",
+ "endpoint_a_port": 2,
+ "endpoint_b_hostname": "switch_2",
+ "endpoint_b_port": 8
+ },
+ {
+ "endpoint_a_hostname": "switch_1",
+ "endpoint_a_port": 1,
+ "endpoint_b_hostname": "domain_controller",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_1",
+ "endpoint_a_port": 2,
+ "endpoint_b_hostname": "web_server",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_1",
+ "endpoint_a_port": 3,
+ "endpoint_b_hostname": "database_server",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_1",
+ "endpoint_a_port": 4,
+ "endpoint_b_hostname": "backup_server",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_1",
+ "endpoint_a_port": 7,
+ "endpoint_b_hostname": "security_suite",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_2",
+ "endpoint_a_port": 1,
+ "endpoint_b_hostname": "client_1",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_2",
+ "endpoint_a_port": 2,
+ "endpoint_b_hostname": "client_2",
+ "endpoint_b_port": 1
+ },
+ {
+ "endpoint_a_hostname": "switch_2",
+ "endpoint_a_port": 7,
+ "endpoint_b_hostname": "security_suite",
+ "endpoint_b_port": 2
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/benchmark/utils.py b/benchmark/utils.py
new file mode 100644
index 00000000..2e92d80d
--- /dev/null
+++ b/benchmark/utils.py
@@ -0,0 +1,47 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+import platform
+from typing import Dict
+
+import psutil
+from GPUtil import GPUtil
+
+
+def get_size(size_bytes: int) -> str:
+ """
+ Scale bytes to its proper format.
+
+ e.g:
+ 1253656 => '1.20MB'
+ 1253656678 => '1.17GB'
+
+ :
+ """
+ factor = 1024
+ for unit in ["", "K", "M", "G", "T", "P"]:
+ if size_bytes < factor:
+ return f"{size_bytes:.2f}{unit}B"
+ size_bytes /= factor
+
+
+def _get_system_info() -> Dict:
+ """Builds and returns a dict containing system info."""
+ uname = platform.uname()
+ cpu_freq = psutil.cpu_freq()
+ virtual_mem = psutil.virtual_memory()
+ swap_mem = psutil.swap_memory()
+ gpus = GPUtil.getGPUs()
+ return {
+ "System": {
+ "OS": uname.system,
+ "OS Version": uname.version,
+ "Machine": uname.machine,
+ "Processor": uname.processor,
+ },
+ "CPU": {
+ "Physical Cores": psutil.cpu_count(logical=False),
+ "Total Cores": psutil.cpu_count(logical=True),
+ "Max Frequency": f"{cpu_freq.max:.2f}Mhz",
+ },
+ "Memory": {"Total": get_size(virtual_mem.total), "Swap Total": get_size(swap_mem.total)},
+ "GPU": [{"Name": gpu.name, "Total Memory": f"{gpu.memoryTotal}MB"} for gpu in gpus],
+ }
diff --git a/docs/index.rst b/docs/index.rst
index 14d8608e..5749ad56 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -54,7 +54,7 @@ It is agnostic to the number of agents, their action / observation spaces, and t
It presents a public API providing a method for describing the current state of the simulation, a method that accepts action requests and provides responses, and a method that triggers a timestep advancement.
The Game Layer converts the simulation into a playable game for the agent(s).
-it translates between simulation state and Gymnasium.Spaces to pass action / observation data between the agent(s) and the simulation. It is responsible for calculating rewards, managing Multi-Agent RL (MARL) action turns, and via a single agent interface can interact with Blue, Red and Green agents.
+It translates between simulation state and Gymnasium.Spaces to pass action / observation data between the agent(s) and the simulation. It is responsible for calculating rewards, managing Multi-Agent RL (MARL) action turns, and via a single agent interface can interact with Blue, Red and Green agents.
Agents can either generate their own scripted behaviour or accept input behaviour from an RL agent.
diff --git a/docs/source/about.rst b/docs/source/about.rst
index 782103d6..cc247623 100644
--- a/docs/source/about.rst
+++ b/docs/source/about.rst
@@ -1,6 +1,6 @@
.. only:: comment
- © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
+ © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
.. _about:
diff --git a/docs/source/example_notebooks.rst b/docs/source/example_notebooks.rst
index 4a26641e..731ea566 100644
--- a/docs/source/example_notebooks.rst
+++ b/docs/source/example_notebooks.rst
@@ -77,6 +77,6 @@ The following extensions should now be installed
: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
+VSCode will then ask for a Python environment version to use. PrimAITE is compatible with Python versions 3.8 - 3.11
You should now be able to interact with the notebook.
diff --git a/docs/source/simulation_components/system/applications/nmap.rst b/docs/source/simulation_components/system/applications/nmap.rst
new file mode 100644
index 00000000..9ea0c60e
--- /dev/null
+++ b/docs/source/simulation_components/system/applications/nmap.rst
@@ -0,0 +1,347 @@
+.. only:: comment
+
+ © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+
+.. _NMAP:
+
+NMAP
+====
+
+Overview
+--------
+
+The NMAP application is used to simulate network scanning activities. NMAP is a powerful tool that helps in discovering
+hosts and services on a network. It provides functionalities such as ping scans to discover active hosts and port scans
+to detect open ports on those hosts.
+
+The NMAP application is essential for network administrators and security professionals to map out a network's
+structure, identify active devices, and find potential vulnerabilities by discovering open ports and running services.
+However, it is also a tool frequently used by attackers during the reconnaissance stage of a cyber attack to gather
+information about the target network.
+
+Scan Types
+----------
+
+Ping Scan
+^^^^^^^^^
+
+A ping scan is used to identify which hosts on a network are active and reachable. This is achieved by sending ICMP
+Echo Request packets (ping) to the target IP addresses. If a host responds with an ICMP Echo Reply, it is considered
+active. Ping scans are useful for quickly mapping out live hosts in a network.
+
+Port Scan
+^^^^^^^^^
+
+A port scan is used to detect open ports on a target host or range of hosts. Open ports can indicate running services
+that might be exploitable or require securing. Port scans help in understanding the services available on a network and
+identifying potential entry points for attacks. There are three types of port scans based on the scope:
+
+- **Horizontal Port Scan**: This scan targets a specific port across a range of IP addresses. It helps in identifying
+ which hosts have a particular service running.
+
+- **Vertical Port Scan**: This scan targets multiple ports on a single IP address. It provides detailed information
+ about the services running on a specific host.
+
+- **Box Scan**: This combines both horizontal and vertical scans, targeting multiple ports across multiple IP addresses.
+ It gives a comprehensive view of the network's service landscape.
+
+Example Usage
+-------------
+
+The network we use for these examples is defined below:
+
+.. code-block:: python
+
+ from ipaddress import IPv4Network
+
+ from primaite.simulator.network.container import Network
+ from primaite.simulator.network.hardware.nodes.host.computer import Computer
+ from primaite.simulator.network.hardware.nodes.network.router import Router
+ from primaite.simulator.network.hardware.nodes.network.switch import Switch
+ from primaite.simulator.system.applications.nmap import NMAP
+ from primaite.simulator.system.services.database.database_service import DatabaseService
+
+ # Initialize the network
+ network = Network()
+
+ # Set up the router
+ router = Router(hostname="router", start_up_duration=0)
+ router.power_on()
+ router.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
+
+ # Set up PC 1
+ pc_1 = Computer(
+ hostname="pc_1",
+ ip_address="192.168.1.11",
+ subnet_mask="255.255.255.0",
+ default_gateway="192.168.1.1",
+ start_up_duration=0
+ )
+ pc_1.power_on()
+
+ # Set up PC 2
+ pc_2 = Computer(
+ hostname="pc_2",
+ ip_address="192.168.1.12",
+ subnet_mask="255.255.255.0",
+ default_gateway="192.168.1.1",
+ start_up_duration=0
+ )
+ pc_2.power_on()
+ pc_2.software_manager.install(DatabaseService)
+ pc_2.software_manager.software["DatabaseService"].start() # start the postgres server
+
+ # Set up PC 3
+ pc_3 = Computer(
+ hostname="pc_3",
+ ip_address="192.168.1.13",
+ subnet_mask="255.255.255.0",
+ default_gateway="192.168.1.1",
+ start_up_duration=0
+ )
+ # Don't power on PC 3
+
+ # Set up the switch
+ switch = Switch(hostname="switch", start_up_duration=0)
+ switch.power_on()
+
+ # Connect devices
+ network.connect(router.network_interface[1], switch.network_interface[24])
+ network.connect(switch.network_interface[1], pc_1.network_interface[1])
+ network.connect(switch.network_interface[2], pc_2.network_interface[1])
+ network.connect(switch.network_interface[3], pc_3.network_interface[1])
+
+
+ pc_1_nmap: NMAP = pc_1.software_manager.software["NMAP"]
+
+
+Ping Scan
+^^^^^^^^^
+
+Perform a ping scan to find active hosts in the `192.168.1.0/24` subnet:
+
+.. code-block:: python
+ :caption: Ping Scan Code
+
+ active_hosts = pc_1_nmap.ping_scan(target_ip_address=IPv4Network("192.168.1.0/24"))
+
+.. code-block:: python
+ :caption: Ping Scan Return Value
+
+ [
+ IPv4Address('192.168.1.11'),
+ IPv4Address('192.168.1.12'),
+ IPv4Address('192.168.1.1')
+ ]
+
+.. code-block:: text
+ :caption: Ping Scan Output
+
+ +-------------------------+
+ | pc_1 NMAP Ping Scan |
+ +--------------+----------+
+ | IP Address | Can Ping |
+ +--------------+----------+
+ | 192.168.1.1 | True |
+ | 192.168.1.11 | True |
+ | 192.168.1.12 | True |
+ +--------------+----------+
+
+Horizontal Port Scan
+^^^^^^^^^^^^^^^^^^^^
+
+Perform a horizontal port scan on port 5432 across multiple IP addresses:
+
+.. code-block:: python
+ :caption: Horizontal Port Scan Code
+
+ horizontal_scan_results = pc_1_nmap.port_scan(
+ target_ip_address=[IPv4Address("192.168.1.12"), IPv4Address("192.168.1.13")],
+ target_port=Port(5432 )
+ )
+
+.. code-block:: python
+ :caption: Horizontal Port Scan Return Value
+
+ {
+ IPv4Address('192.168.1.12'): {
+ : [
+
+ ]
+ }
+ }
+
+.. code-block:: text
+ :caption: Horizontal Port Scan Output
+
+ +--------------------------------------------------+
+ | pc_1 NMAP Port Scan (Horizontal) |
+ +--------------+------+-----------------+----------+
+ | IP Address | Port | Name | Protocol |
+ +--------------+------+-----------------+----------+
+ | 192.168.1.12 | 5432 | POSTGRES_SERVER | TCP |
+ +--------------+------+-----------------+----------+
+
+Vertical Post Scan
+^^^^^^^^^^^^^^^^^^
+
+Perform a vertical port scan on multiple ports on a single IP address:
+
+.. code-block:: python
+ :caption: Vertical Port Scan Code
+
+ vertical_scan_results = pc_1_nmap.port_scan(
+ target_ip_address=[IPv4Address("192.168.1.12")],
+ target_port=[Port(21), Port(22), Port(80), Port(443)]
+ )
+
+.. code-block:: python
+ :caption: Vertical Port Scan Return Value
+
+ {
+ IPv4Address('192.168.1.12'): {
+ : [
+ ,
+
+ ]
+ }
+ }
+
+.. code-block:: text
+ :caption: Vertical Port Scan Output
+
+ +---------------------------------------+
+ | pc_1 NMAP Port Scan (Vertical) |
+ +--------------+------+------+----------+
+ | IP Address | Port | Name | Protocol |
+ +--------------+------+------+----------+
+ | 192.168.1.12 | 21 | FTP | TCP |
+ | 192.168.1.12 | 80 | HTTP | TCP |
+ +--------------+------+------+----------+
+
+Box Scan
+^^^^^^^^
+
+Perform a box scan on multiple ports across multiple IP addresses:
+
+.. code-block:: python
+ :caption: Box Port Scan Code
+
+ # Power PC 3 on before performing the box scan
+ pc_3.power_on()
+
+
+ box_scan_results = pc_1_nmap.port_scan(
+ target_ip_address=[IPv4Address("192.168.1.12"), IPv4Address("192.168.1.13")],
+ target_port=[Port(21), Port(22), Port(80), Port(443)]
+ )
+
+.. code-block:: python
+ :caption: Box Port Scan Return Value
+
+ {
+ IPv4Address('192.168.1.13'): {
+ : [
+ ,
+
+ ]
+ },
+ IPv4Address('192.168.1.12'): {
+ : [
+ ,
+
+ ]
+ }
+ }
+
+.. code-block:: text
+ :caption: Box Port Scan Output
+
+ +---------------------------------------+
+ | pc_1 NMAP Port Scan (Box) |
+ +--------------+------+------+----------+
+ | IP Address | Port | Name | Protocol |
+ +--------------+------+------+----------+
+ | 192.168.1.12 | 21 | FTP | TCP |
+ | 192.168.1.12 | 80 | HTTP | TCP |
+ | 192.168.1.13 | 21 | FTP | TCP |
+ | 192.168.1.13 | 80 | HTTP | TCP |
+ +--------------+------+------+----------+
+
+Full Box Scan
+^^^^^^^^^^^^^
+
+Perform a full box scan on all ports, over both TCP and UDP, on a whole subnet:
+
+.. code-block:: python
+ :caption: Box Port Scan Code
+
+ # Power PC 3 on before performing the full box scan
+ pc_3.power_on()
+
+
+ full_box_scan_results = pc_1_nmap.port_scan(
+ target_ip_address=IPv4Network("192.168.1.0/24"),
+ )
+
+.. code-block:: python
+ :caption: Box Port Scan Return Value
+
+ {
+ IPv4Address('192.168.1.11'): {
+ : [
+
+ ]
+ },
+ IPv4Address('192.168.1.1'): {
+ : [
+
+ ]
+ },
+ IPv4Address('192.168.1.12'): {
+ : [
+ ,
+ ,
+ ,
+
+ ],
+ : [
+ ,
+
+ ]
+ },
+ IPv4Address('192.168.1.13'): {
+ : [
+ ,
+ ,
+
+ ],
+ : [
+ ,
+
+ ]
+ }
+ }
+
+.. code-block:: text
+ :caption: Box Port Scan Output
+
+ +--------------------------------------------------+
+ | pc_1 NMAP Port Scan (Box) |
+ +--------------+------+-----------------+----------+
+ | IP Address | Port | Name | Protocol |
+ +--------------+------+-----------------+----------+
+ | 192.168.1.1 | 219 | ARP | UDP |
+ | 192.168.1.11 | 219 | ARP | UDP |
+ | 192.168.1.12 | 21 | FTP | TCP |
+ | 192.168.1.12 | 53 | DNS | TCP |
+ | 192.168.1.12 | 80 | HTTP | TCP |
+ | 192.168.1.12 | 123 | NTP | UDP |
+ | 192.168.1.12 | 219 | ARP | UDP |
+ | 192.168.1.12 | 5432 | POSTGRES_SERVER | TCP |
+ | 192.168.1.13 | 21 | FTP | TCP |
+ | 192.168.1.13 | 53 | DNS | TCP |
+ | 192.168.1.13 | 80 | HTTP | TCP |
+ | 192.168.1.13 | 123 | NTP | UDP |
+ | 192.168.1.13 | 219 | ARP | UDP |
+ +--------------+------+-----------------+----------+
diff --git a/docs/source/varying_config_files.rst b/docs/source/varying_config_files.rst
index 9e4f97b6..d8f77f64 100644
--- a/docs/source/varying_config_files.rst
+++ b/docs/source/varying_config_files.rst
@@ -24,7 +24,7 @@ For each variation that could be used in a placeholder, there is a separate yaml
The data that fills the placeholder is defined as a YAML Anchor in a separate file, denoted by an ampersand ``&anchor``.
-Learn more about YAML Aliases and Anchors here.
+Learn more about YAML Aliases and Anchors `here `_.
Schedule
********
diff --git a/pyproject.toml b/pyproject.toml
index d01299be..290720bc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -33,7 +33,7 @@ dependencies = [
"numpy==1.23.5",
"platformdirs==3.5.1",
"plotly==5.15.0",
- "polars==0.18.4",
+ "polars==0.20.30",
"prettytable==3.8.0",
"PyYAML==6.0",
"typer[all]==0.9.0",
diff --git a/src/primaite/VERSION b/src/primaite/VERSION
index 9e0b71d0..4a36342f 100644
--- a/src/primaite/VERSION
+++ b/src/primaite/VERSION
@@ -1 +1 @@
-3.0.0b9
+3.0.0
diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py
index e9cb82ae..f63926c2 100644
--- a/src/primaite/game/agent/actions.py
+++ b/src/primaite/game/agent/actions.py
@@ -11,7 +11,7 @@ AbstractAction. The ActionManager is responsible for:
"""
import itertools
from abc import ABC, abstractmethod
-from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
+from typing import Dict, List, Optional, Tuple, TYPE_CHECKING, Union
from gymnasium import spaces
@@ -871,6 +871,74 @@ class NetworkPortDisableAction(AbstractAction):
return ["network", "node", target_nodename, "network_interface", port_id, "disable"]
+class NodeNMAPPingScanAction(AbstractAction):
+ """Action which performs an NMAP ping scan."""
+
+ def __init__(self, manager: "ActionManager", **kwargs) -> None:
+ super().__init__(manager=manager)
+
+ def form_request(self, source_node: str, target_ip_address: Union[str, List[str]]) -> List[str]: # noqa
+ return [
+ "network",
+ "node",
+ source_node,
+ "application",
+ "NMAP",
+ "ping_scan",
+ {"target_ip_address": target_ip_address},
+ ]
+
+
+class NodeNMAPPortScanAction(AbstractAction):
+ """Action which performs an NMAP port scan."""
+
+ def __init__(self, manager: "ActionManager", **kwargs) -> None:
+ super().__init__(manager=manager)
+
+ def form_request(
+ self,
+ source_node: str,
+ target_ip_address: Union[str, List[str]],
+ target_protocol: Optional[Union[str, List[str]]] = None,
+ target_port: Optional[Union[str, List[str]]] = None,
+ ) -> List[str]: # noqa
+ """Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
+ return [
+ "network",
+ "node",
+ source_node,
+ "application",
+ "NMAP",
+ "port_scan",
+ {"target_ip_address": target_ip_address, "target_port": target_port, "target_protocol": target_protocol},
+ ]
+
+
+class NodeNetworkServiceReconAction(AbstractAction):
+ """Action which performs an NMAP network service recon (ping scan followed by port scan)."""
+
+ def __init__(self, manager: "ActionManager", **kwargs) -> None:
+ super().__init__(manager=manager)
+
+ def form_request(
+ self,
+ source_node: str,
+ target_ip_address: Union[str, List[str]],
+ target_protocol: Optional[Union[str, List[str]]] = None,
+ target_port: Optional[Union[str, List[str]]] = None,
+ ) -> List[str]: # noqa
+ """Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
+ return [
+ "network",
+ "node",
+ source_node,
+ "application",
+ "NMAP",
+ "network_service_recon",
+ {"target_ip_address": target_ip_address, "target_port": target_port, "target_protocol": target_protocol},
+ ]
+
+
class ActionManager:
"""Class which manages the action space for an agent."""
@@ -916,6 +984,9 @@ class ActionManager:
"HOST_NIC_DISABLE": HostNICDisableAction,
"NETWORK_PORT_ENABLE": NetworkPortEnableAction,
"NETWORK_PORT_DISABLE": NetworkPortDisableAction,
+ "NODE_NMAP_PING_SCAN": NodeNMAPPingScanAction,
+ "NODE_NMAP_PORT_SCAN": NodeNMAPPortScanAction,
+ "NODE_NMAP_NETWORK_SERVICE_RECON": NodeNetworkServiceReconAction,
}
"""Dictionary which maps action type strings to the corresponding action class."""
diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py
index d4c27967..2a889993 100644
--- a/src/primaite/game/game.py
+++ b/src/primaite/game/game.py
@@ -361,11 +361,6 @@ class PrimaiteGame:
server_ip_address=IPv4Address(opt.get("server_ip")),
server_password=opt.get("server_password"),
payload=opt.get("payload", "ENCRYPT"),
- c2_beacon_p_of_success=float(opt.get("c2_beacon_p_of_success", "0.5")),
- target_scan_p_of_success=float(opt.get("target_scan_p_of_success", "0.1")),
- ransomware_encrypt_p_of_success=float(
- opt.get("ransomware_encrypt_p_of_success", "0.1")
- ),
)
elif application_type == "DatabaseClient":
if "options" in application_cfg:
diff --git a/src/primaite/interface/request.py b/src/primaite/interface/request.py
index 8d6d1ca2..41875779 100644
--- a/src/primaite/interface/request.py
+++ b/src/primaite/interface/request.py
@@ -3,7 +3,7 @@ from typing import Dict, ForwardRef, List, Literal, Union
from pydantic import BaseModel, ConfigDict, StrictBool, validate_call
-RequestFormat = List[Union[str, int, float]]
+RequestFormat = List[Union[str, int, float, Dict]]
RequestResponse = ForwardRef("RequestResponse")
"""This makes it possible to type-hint RequestResponse.from_bool return type."""
diff --git a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb
index 21d67bab..33d56fb0 100644
--- a/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb
+++ b/src/primaite/notebooks/Data-Manipulation-Customising-Red-Agent.ipynb
@@ -4,13 +4,15 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Customising red agents\n",
+ "# Customising UC2 Red Agents\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
"\n",
"This notebook will go over some examples of how red agent behaviour can be varied by changing its configuration parameters.\n",
"\n",
"First, let's load the standard Data Manipulation config file, and see what the red agent does.\n",
"\n",
- "*(For a full explanation of the Data Manipulation scenario, check out the notebook `Data-Manipulation-E2E-Demonstration.ipynb`)*"
+ "*(For a full explanation of the Data Manipulation scenario, check out the data manipulation scenario notebook)*"
]
},
{
diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb
index e0f79795..b3a90cc0 100644
--- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb
+++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb
@@ -4,7 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Data Manipulation Scenario\n"
+ "# Data Manipulation Scenario\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK"
]
},
{
@@ -79,7 +81,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Reinforcement learning details"
+ "## Reinforcement learning details"
]
},
{
@@ -692,7 +694,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb
index 25dec889..a832f3cc 100644
--- a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb
+++ b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb
@@ -4,7 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Getting information out of PrimAITE"
+ "# Getting information out of PrimAITE\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n"
]
},
{
@@ -160,7 +162,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.8.10"
+ "version": "3.10.8"
}
},
"nbformat": 4,
diff --git a/src/primaite/notebooks/Requests-and-Responses.ipynb b/src/primaite/notebooks/Requests-and-Responses.ipynb
index aa3fddf9..ca9f02f5 100644
--- a/src/primaite/notebooks/Requests-and-Responses.ipynb
+++ b/src/primaite/notebooks/Requests-and-Responses.ipynb
@@ -6,7 +6,9 @@
"source": [
"# Requests and Responses\n",
"\n",
- "Agents interact with the PrimAITE simulation via the Request system.\n"
+ "Agents interact with the PrimAITE simulation via the Request system.\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n"
]
},
{
diff --git a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb
index 5ffb19ad..c185b8b5 100644
--- a/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb
+++ b/src/primaite/notebooks/Training-an-RLLIB-MARL-System.ipynb
@@ -4,7 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Train a Multi agent system using RLLIB\n",
+ "# Train a Multi agent system using RLLIB\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
"\n",
"This notebook will demonstrate how to use the `PrimaiteRayMARLEnv` to train a very basic system with two PPO agents."
]
diff --git a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb
index fbc5f4c6..bdd60f36 100644
--- a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb
+++ b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb
@@ -4,7 +4,10 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Train a Single agent system using RLLib\n",
+ "# Train a Single agent system using RLLib\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
"This notebook will demonstrate how to use PrimaiteRayEnv to train a basic PPO agent."
]
},
diff --git a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb
index 1e247e81..892736fe 100644
--- a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb
+++ b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb
@@ -6,6 +6,8 @@
"source": [
"# Training an SB3 Agent\n",
"\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
"This notebook will demonstrate how to use primaite to create and train a PPO agent, using a pre-defined configuration file."
]
},
@@ -166,7 +168,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -180,7 +182,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.12"
+ "version": "3.10.8"
}
},
"nbformat": 4,
diff --git a/src/primaite/notebooks/Using-Episode-Schedules.ipynb b/src/primaite/notebooks/Using-Episode-Schedules.ipynb
index fc9e04f7..0d0f1a4a 100644
--- a/src/primaite/notebooks/Using-Episode-Schedules.ipynb
+++ b/src/primaite/notebooks/Using-Episode-Schedules.ipynb
@@ -6,6 +6,8 @@
"source": [
"# Using Episode Schedules\n",
"\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
"PrimAITE supports the ability to use different variations on a scenario at different episodes. This can be used to increase \n",
"domain randomisation to prevent overfitting, or to set up curriculum learning to train agents to perform more complicated tasks.\n",
"\n",
diff --git a/src/primaite/notebooks/multi-processing.ipynb b/src/primaite/notebooks/multi-processing.ipynb
index 2b806e7c..86b549a7 100644
--- a/src/primaite/notebooks/multi-processing.ipynb
+++ b/src/primaite/notebooks/multi-processing.ipynb
@@ -4,7 +4,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Simple multi-processing demo using SubprocVecEnv from SB3"
+ "# Simple multi-processing demonstration\n",
+ "\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
+ "This notebook uses SubprocVecEnv from SB3."
]
},
{
@@ -139,7 +143,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.11"
+ "version": "3.10.12"
}
},
"nbformat": 4,
diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py
index 2d9030f9..2a0f40ca 100644
--- a/src/primaite/session/environment.py
+++ b/src/primaite/session/environment.py
@@ -38,6 +38,8 @@ class PrimaiteGymEnv(gymnasium.Env):
"""Name of the RL agent. Since there should only be one RL agent we can just pull the first and only key."""
self.episode_counter: int = 0
"""Current episode number."""
+ self.total_reward_per_episode: Dict[int, float] = {}
+ """Average rewards of agents per episode."""
@property
def agent(self) -> ProxyAgent:
@@ -90,6 +92,8 @@ class PrimaiteGymEnv(gymnasium.Env):
f"Resetting environment, episode {self.episode_counter}, "
f"avg. reward: {self.agent.reward_function.total_reward}"
)
+ self.total_reward_per_episode[self.episode_counter] = self.agent.reward_function.total_reward
+
if self.io.settings.save_agent_actions:
all_agent_actions = {name: agent.history for name, agent in self.game.agents.items()}
self.io.write_agent_log(agent_actions=all_agent_actions, episode=self.episode_counter)
diff --git a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb
index 31173022..9f4abbf3 100644
--- a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb
+++ b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb
@@ -6,7 +6,9 @@
"source": [
"# Build a simulation using the Python API\n",
"\n",
- "Currently, this notebook manipulates the simulation by directly placing objects inside of the attributes of the network and domain. It should be refactored when proper methods exist for adding these objects.\n"
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
+ "Currently, this notebook manipulates the simulation by directly placing objects inside of the attributes of the network and domain. It should be refactored when proper methods exist for adding these objects."
]
},
{
diff --git a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb
index 22fdf7ce..17a0f796 100644
--- a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb
+++ b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb
@@ -7,6 +7,8 @@
"source": [
"# PrimAITE Router Simulation Demo\n",
"\n",
+ "© Crown-owned copyright 2024, Defence Science and Technology Laboratory UK\n",
+ "\n",
"This demo uses a modified version of the ARCD Use Case 2 Network (seen below) to demonstrate the capabilities of the Network simulator in PrimAITE."
]
},
diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py
index fc7ff87e..551a30b7 100644
--- a/src/primaite/simulator/core.py
+++ b/src/primaite/simulator/core.py
@@ -222,7 +222,7 @@ class SimComponent(BaseModel):
return state
@validate_call
- def apply_request(self, request: RequestFormat, context: Dict = {}) -> RequestResponse:
+ def apply_request(self, request: RequestFormat, context: Optional[Dict] = None) -> RequestResponse:
"""
Apply a request to a simulation component. Request data is passed in as a 'namespaced' list of strings.
@@ -240,6 +240,8 @@ class SimComponent(BaseModel):
:param: context: Dict containing context for requests
:type context: Dict
"""
+ if not context:
+ context = None
if self._request_manager is None:
return
return self._request_manager(request, context)
diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py
index cc42d844..9cde35f6 100644
--- a/src/primaite/simulator/network/hardware/base.py
+++ b/src/primaite/simulator/network/hardware/base.py
@@ -29,6 +29,7 @@ from primaite.simulator.network.nmne import (
NMNE_CAPTURE_KEYWORDS,
)
from primaite.simulator.network.transmission.data_link_layer import Frame
+from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.system.applications.application import Application
from primaite.simulator.system.core.packet_capture import PacketCapture
from primaite.simulator.system.core.session_manager import SessionManager
@@ -37,6 +38,7 @@ from primaite.simulator.system.core.sys_log import SysLog
from primaite.simulator.system.processes.process import Process
from primaite.simulator.system.services.service import Service
from primaite.simulator.system.software import IOSoftware
+from primaite.utils.converters import convert_dict_enum_keys_to_enum_values
from primaite.utils.validators import IPV4Address
IOSoftwareClass = TypeVar("IOSoftwareClass", bound=IOSoftware)
@@ -108,10 +110,14 @@ class NetworkInterface(SimComponent, ABC):
nmne: Dict = Field(default_factory=lambda: {})
"A dict containing details of the number of malicious network events captured."
+ traffic: Dict = Field(default_factory=lambda: {})
+ "A dict containing details of the inbound and outbound traffic by port and protocol."
+
def setup_for_episode(self, episode: int):
"""Reset the original state of the SimComponent."""
super().setup_for_episode(episode=episode)
self.nmne = {}
+ self.traffic = {}
if episode and self.pcap and SIM_OUTPUT.save_pcap_logs:
self.pcap.current_episode = episode
self.pcap.setup_logger()
@@ -147,6 +153,7 @@ class NetworkInterface(SimComponent, ABC):
)
if CAPTURE_NMNE:
state.update({"nmne": {k: v for k, v in self.nmne.items()}})
+ state.update({"traffic": convert_dict_enum_keys_to_enum_values(self.traffic)})
return state
@abstractmethod
@@ -237,6 +244,47 @@ class NetworkInterface(SimComponent, ABC):
# Increment a generic counter if keyword capturing is not enabled
keyword_level["*"] = keyword_level.get("*", 0) + 1
+ def _capture_traffic(self, frame: Frame, inbound: bool = True):
+ """
+ Capture traffic statistics at the Network Interface.
+
+ :param frame: The network frame containing the traffic data.
+ :type frame: Frame
+ :param inbound: Flag indicating if the traffic is inbound or outbound. Defaults to True.
+ :type inbound: bool
+ """
+ # Determine the direction of the traffic
+ direction = "inbound" if inbound else "outbound"
+
+ # Initialize protocol and port variables
+ protocol = None
+ port = None
+
+ # Identify the protocol and port from the frame
+ if frame.tcp:
+ protocol = IPProtocol.TCP
+ port = frame.tcp.dst_port
+ elif frame.udp:
+ protocol = IPProtocol.UDP
+ port = frame.udp.dst_port
+ elif frame.icmp:
+ protocol = IPProtocol.ICMP
+
+ # Ensure the protocol is in the capture dict
+ if protocol not in self.traffic:
+ self.traffic[protocol] = {}
+
+ # Handle non-ICMP protocols that use ports
+ if protocol != IPProtocol.ICMP:
+ if port not in self.traffic[protocol]:
+ self.traffic[protocol][port] = {"inbound": 0, "outbound": 0}
+ self.traffic[protocol][port][direction] += frame.size
+ else:
+ # Handle ICMP protocol separately (ICMP does not use ports)
+ if not self.traffic[protocol]:
+ self.traffic[protocol] = {"inbound": 0, "outbound": 0}
+ self.traffic[protocol][direction] += frame.size
+
@abstractmethod
def send_frame(self, frame: Frame) -> bool:
"""
@@ -246,6 +294,7 @@ class NetworkInterface(SimComponent, ABC):
:return: A boolean indicating whether the frame was successfully sent.
"""
self._capture_nmne(frame, inbound=False)
+ self._capture_traffic(frame, inbound=False)
@abstractmethod
def receive_frame(self, frame: Frame) -> bool:
@@ -256,6 +305,7 @@ class NetworkInterface(SimComponent, ABC):
:return: A boolean indicating whether the frame was successfully received.
"""
self._capture_nmne(frame, inbound=True)
+ self._capture_traffic(frame, inbound=True)
def __str__(self) -> str:
"""
@@ -767,6 +817,24 @@ class Node(SimComponent):
self.session_manager.software_manager = self.software_manager
self._install_system_software()
+ def ip_is_network_interface(self, ip_address: IPv4Address, enabled_only: bool = False) -> bool:
+ """
+ Checks if a given IP address belongs to any of the nodes interfaces.
+
+ :param ip_address: The IP address to check.
+ :param enabled_only: If True, only considers enabled network interfaces.
+ :return: True if the IP address is assigned to one of the nodes interfaces; False otherwise.
+ """
+ for network_interface in self.network_interface.values():
+ if not hasattr(network_interface, "ip_address"):
+ continue
+ if network_interface.ip_address == ip_address:
+ if enabled_only:
+ return network_interface.enabled
+ else:
+ return True
+ return False
+
def setup_for_episode(self, episode: int):
"""Reset the original state of the SimComponent."""
super().setup_for_episode(episode=episode)
diff --git a/src/primaite/simulator/network/hardware/nodes/host/host_node.py b/src/primaite/simulator/network/hardware/nodes/host/host_node.py
index a20dc898..fdb28339 100644
--- a/src/primaite/simulator/network/hardware/nodes/host/host_node.py
+++ b/src/primaite/simulator/network/hardware/nodes/host/host_node.py
@@ -8,6 +8,8 @@ from primaite import getLogger
from primaite.simulator.network.hardware.base import IPWiredNetworkInterface, Link, Node
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
from primaite.simulator.network.transmission.data_link_layer import Frame
+from primaite.simulator.system.applications.application import ApplicationOperatingState
+from primaite.simulator.system.applications.nmap import NMAP
from primaite.simulator.system.applications.web_browser import WebBrowser
from primaite.simulator.system.services.arp.arp import ARP, ARPPacket
from primaite.simulator.system.services.dns.dns_client import DNSClient
@@ -303,6 +305,7 @@ class HostNode(Node):
"DNSClient": DNSClient,
"NTPClient": NTPClient,
"WebBrowser": WebBrowser,
+ "NMAP": NMAP,
}
"""List of system software that is automatically installed on nodes."""
@@ -315,6 +318,16 @@ class HostNode(Node):
super().__init__(**kwargs)
self.connect_nic(NIC(ip_address=ip_address, subnet_mask=subnet_mask))
+ @property
+ def nmap(self) -> Optional[NMAP]:
+ """
+ Return the NMAP application installed on the Node.
+
+ :return: NMAP application installed on the Node.
+ :rtype: Optional[NMAP]
+ """
+ return self.software_manager.software.get("NMAP")
+
@property
def arp(self) -> Optional[ARP]:
"""
@@ -366,8 +379,15 @@ class HostNode(Node):
elif frame.udp:
dst_port = frame.udp.dst_port
+ can_accept_nmap = False
+ if self.software_manager.software.get("NMAP"):
+ if self.software_manager.software["NMAP"].operating_state == ApplicationOperatingState.RUNNING:
+ can_accept_nmap = True
+
+ accept_nmap = can_accept_nmap and frame.payload.__class__.__name__ == "PortScanPayload"
+
accept_frame = False
- if frame.icmp or dst_port in self.software_manager.get_open_ports():
+ if frame.icmp or dst_port in self.software_manager.get_open_ports() or accept_nmap:
# accept the frame as the port is open or if it's an ICMP frame
accept_frame = True
diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py
index c2491760..61b7b96a 100644
--- a/src/primaite/simulator/network/hardware/nodes/network/router.py
+++ b/src/primaite/simulator/network/hardware/nodes/network/router.py
@@ -19,6 +19,7 @@ from primaite.simulator.network.protocols.icmp import ICMPPacket, ICMPType
from primaite.simulator.network.transmission.data_link_layer import Frame
from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port
+from primaite.simulator.system.applications.nmap import NMAP
from primaite.simulator.system.core.session_manager import SessionManager
from primaite.simulator.system.core.sys_log import SysLog
from primaite.simulator.system.services.arp.arp import ARP
@@ -1239,6 +1240,7 @@ class Router(NetworkNode):
icmp.router = self
self.software_manager.install(RouterARP)
self.arp.router = self
+ self.software_manager.install(NMAP)
def _set_default_acl(self):
"""
diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py
index 59ea41aa..bae2139b 100644
--- a/src/primaite/simulator/system/applications/database_client.py
+++ b/src/primaite/simulator/system/applications/database_client.py
@@ -271,9 +271,16 @@ class DatabaseClient(Application):
Calls disconnect on all client connections to ensure that both client and server connections are killed.
"""
- while self.client_connections.values():
- client_connection = self.client_connections[next(iter(self.client_connections.keys()))]
- client_connection.disconnect()
+ while self.client_connections:
+ conn_key = next(iter(self.client_connections.keys()))
+ conn_obj: DatabaseClientConnection = self.client_connections[conn_key]
+ conn_obj.disconnect()
+ if conn_obj.is_active or conn_key in self.client_connections:
+ self.sys_log.error(
+ "Attempted to uninstall database client but could not drop active connections. "
+ "Forcing uninstall anyway."
+ )
+ self.client_connections.pop(conn_key, None)
super().uninstall()
def get_new_connection(self) -> Optional[DatabaseClientConnection]:
diff --git a/src/primaite/simulator/system/applications/nmap.py b/src/primaite/simulator/system/applications/nmap.py
new file mode 100644
index 00000000..77be57e5
--- /dev/null
+++ b/src/primaite/simulator/system/applications/nmap.py
@@ -0,0 +1,452 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+from ipaddress import IPv4Address, IPv4Network
+from typing import Any, Dict, Final, List, Optional, Set, Tuple, Union
+
+from prettytable import PrettyTable
+from pydantic import validate_call
+
+from primaite.interface.request import RequestResponse
+from primaite.simulator.core import RequestManager, RequestType, SimComponent
+from primaite.simulator.network.transmission.network_layer import IPProtocol
+from primaite.simulator.network.transmission.transport_layer import Port
+from primaite.simulator.system.applications.application import Application
+from primaite.utils.validators import IPV4Address
+
+
+class PortScanPayload(SimComponent):
+ """
+ A class representing the payload for a port scan.
+
+ :ivar ip_address: The target IP address for the port scan.
+ :ivar port: The target port for the port scan.
+ :ivar protocol: The protocol used for the port scan.
+ :ivar request:Flag to indicate whether this is a request or not.
+ """
+
+ ip_address: IPV4Address
+ port: Port
+ protocol: IPProtocol
+ request: bool = True
+
+ def describe_state(self) -> Dict:
+ """
+ Describe the state of the port scan payload.
+
+ :return: A dictionary representation of the port scan payload state.
+ :rtype: Dict
+ """
+ state = super().describe_state()
+ state["ip_address"] = str(self.ip_address)
+ state["port"] = self.port.value
+ state["protocol"] = self.protocol.value
+ state["request"] = self.request
+
+ return state
+
+
+class NMAP(Application):
+ """
+ A class representing the NMAP application for network scanning.
+
+ NMAP is a network scanning tool used to discover hosts and services on a network. It provides functionalities such
+ as ping scans to discover active hosts and port scans to detect open ports on those hosts.
+ """
+
+ _active_port_scans: Dict[str, PortScanPayload] = {}
+ _port_scan_responses: Dict[str, PortScanPayload] = {}
+
+ _PORT_SCAN_TYPE_MAP: Final[Dict[Tuple[bool, bool], str]] = {
+ (True, True): "Box",
+ (True, False): "Horizontal",
+ (False, True): "Vertical",
+ (False, False): "Port",
+ }
+
+ def __init__(self, **kwargs):
+ kwargs["name"] = "NMAP"
+ kwargs["port"] = Port.NONE
+ kwargs["protocol"] = IPProtocol.NONE
+ super().__init__(**kwargs)
+
+ def _can_perform_network_action(self) -> bool:
+ """
+ Checks if the NMAP application can perform outbound network actions.
+
+ This is done by checking the parent application can_per_action functionality. Then checking if there is an
+ enabled NIC that can be used for outbound traffic.
+
+ :return: True if outbound network actions can be performed, otherwise False.
+ """
+ if not super()._can_perform_action():
+ return False
+
+ for nic in self.software_manager.node.network_interface.values():
+ if nic.enabled:
+ return True
+ return False
+
+ def _init_request_manager(self) -> RequestManager:
+ def _ping_scan_action(request: List[Any], context: Any) -> RequestResponse:
+ results = self.ping_scan(target_ip_address=request[0]["target_ip_address"], json_serializable=True)
+ if not self._can_perform_network_action():
+ return RequestResponse.from_bool(False)
+ return RequestResponse(
+ status="success",
+ data={"live_hosts": results},
+ )
+
+ def _port_scan_action(request: List[Any], context: Any) -> RequestResponse:
+ results = self.port_scan(**request[0], json_serializable=True)
+ if not self._can_perform_network_action():
+ return RequestResponse.from_bool(False)
+ return RequestResponse(
+ status="success",
+ data=results,
+ )
+
+ def _network_service_recon_action(request: List[Any], context: Any) -> RequestResponse:
+ results = self.network_service_recon(**request[0], json_serializable=True)
+ if not self._can_perform_network_action():
+ return RequestResponse.from_bool(False)
+ return RequestResponse(
+ status="success",
+ data=results,
+ )
+
+ rm = RequestManager()
+
+ rm.add_request(
+ name="ping_scan",
+ request_type=RequestType(func=_ping_scan_action),
+ )
+
+ rm.add_request(
+ name="port_scan",
+ request_type=RequestType(func=_port_scan_action),
+ )
+
+ rm.add_request(
+ name="network_service_recon",
+ request_type=RequestType(func=_network_service_recon_action),
+ )
+
+ return rm
+
+ def describe_state(self) -> Dict:
+ """
+ Describe the state of the NMAP application.
+
+ :return: A dictionary representation of the NMAP application's state.
+ :rtype: Dict
+ """
+ return super().describe_state()
+
+ @staticmethod
+ def _explode_ip_address_network_array(
+ target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]]
+ ) -> Set[IPv4Address]:
+ """
+ Explode a mixed array of IP addresses and networks into a set of individual IP addresses.
+
+ This method takes a combination of single and lists of IPv4 addresses and IPv4 networks, expands any networks
+ into their constituent subnet useable IP addresses, and returns a set of unique IP addresses. Broadcast and
+ network addresses are excluded from the result.
+
+ :param target_ip_address: A single or list of IPv4 addresses and networks.
+ :type target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]]
+ :return: A set of unique IPv4 addresses expanded from the input.
+ :rtype: Set[IPv4Address]
+ """
+ if isinstance(target_ip_address, IPv4Address) or isinstance(target_ip_address, IPv4Network):
+ target_ip_address = [target_ip_address]
+ ip_addresses: List[IPV4Address] = []
+ for ip_address in target_ip_address:
+ if isinstance(ip_address, IPv4Network):
+ ip_addresses += [
+ ip
+ for ip in ip_address.hosts()
+ if not ip == ip_address.broadcast_address and not ip == ip_address.network_address
+ ]
+ else:
+ ip_addresses.append(ip_address)
+ return set(ip_addresses)
+
+ @validate_call()
+ def ping_scan(
+ self,
+ target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]],
+ show: bool = True,
+ show_online_only: bool = True,
+ json_serializable: bool = False,
+ ) -> Union[List[IPV4Address], List[str]]:
+ """
+ Perform a ping scan on the target IP address(es).
+
+ :param target_ip_address: The target IP address(es) or network(s) for the ping scan.
+ :type target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]]
+ :param show: Flag indicating whether to display the scan results. Defaults to True.
+ :type show: bool
+ :param show_online_only: Flag indicating whether to show only the online hosts. Defaults to True.
+ :type show_online_only: bool
+ :param json_serializable: Flag indicating whether the return value should be json serializable. Defaults to
+ False.
+ :type json_serializable: bool
+
+ :return: A list of active IP addresses that responded to the ping.
+ :rtype: Union[List[IPV4Address], List[str]]
+ """
+ active_nodes = []
+ if show:
+ table = PrettyTable(["IP Address", "Can Ping"])
+ table.align = "l"
+ table.title = f"{self.software_manager.node.hostname} NMAP Ping Scan"
+
+ ip_addresses = self._explode_ip_address_network_array(target_ip_address)
+
+ for ip_address in ip_addresses:
+ # Prevent ping scan on this node
+ if self.software_manager.node.ip_is_network_interface(ip_address=ip_address):
+ continue
+ can_ping = self.software_manager.icmp.ping(ip_address)
+ if can_ping:
+ active_nodes.append(ip_address if not json_serializable else str(ip_address))
+ if show and (can_ping or not show_online_only):
+ table.add_row([ip_address, can_ping])
+ if show:
+ print(table.get_string(sortby="IP Address"))
+ return active_nodes
+
+ def _determine_port_scan_type(self, target_ip_addresses: List[IPV4Address], target_ports: List[Port]) -> str:
+ """
+ Determine the type of port scan based on the number of target IP addresses and ports.
+
+ :param target_ip_addresses: The list of target IP addresses.
+ :type target_ip_addresses: List[IPV4Address]
+ :param target_ports: The list of target ports.
+ :type target_ports: List[Port]
+
+ :return: The type of port scan.
+ :rtype: str
+ """
+ vertical_scan = len(target_ports) > 1
+ horizontal_scan = len(target_ip_addresses) > 1
+
+ return self._PORT_SCAN_TYPE_MAP[horizontal_scan, vertical_scan]
+
+ def _check_port_open_on_ip_address(
+ self,
+ ip_address: IPv4Address,
+ port: Port,
+ protocol: IPProtocol,
+ is_re_attempt: bool = False,
+ port_scan_uuid: Optional[str] = None,
+ ) -> bool:
+ """
+ Check if a port is open on a specific IP address.
+
+ :param ip_address: The target IP address.
+ :type ip_address: IPv4Address
+ :param port: The target port.
+ :type port: Port
+ :param protocol: The protocol used for the port scan.
+ :type protocol: IPProtocol
+ :param is_re_attempt: Flag indicating if this is a reattempt. Defaults to False.
+ :type is_re_attempt: bool
+ :param port_scan_uuid: The UUID of the port scan payload. Defaults to None.
+ :type port_scan_uuid: Optional[str]
+
+ :return: True if the port is open, False otherwise.
+ :rtype: bool
+ """
+ # The recursive base case
+ if is_re_attempt:
+ # Return True if a response has been received, otherwise return False
+ if port_scan_uuid in self._port_scan_responses:
+ self._port_scan_responses.pop(port_scan_uuid)
+ return True
+ return False
+
+ # Send the port scan request
+ payload = PortScanPayload(ip_address=ip_address, port=port, protocol=protocol)
+ self._active_port_scans[payload.uuid] = payload
+ self.sys_log.info(
+ f"{self.name}: Sending port scan request over {payload.protocol.name} on port {payload.port.value} "
+ f"({payload.port.name}) to {payload.ip_address}"
+ )
+ self.software_manager.send_payload_to_session_manager(
+ payload=payload, dest_ip_address=ip_address, src_port=port, dest_port=port, ip_protocol=protocol
+ )
+
+ # Recursively call this function with as a reattempt
+ return self._check_port_open_on_ip_address(
+ ip_address=ip_address, port=port, protocol=protocol, is_re_attempt=True, port_scan_uuid=payload.uuid
+ )
+
+ def _process_port_scan_response(self, payload: PortScanPayload):
+ """
+ Process the response to a port scan request.
+
+ :param payload: The port scan payload received in response.
+ :type payload: PortScanPayload
+ """
+ if payload.uuid in self._active_port_scans:
+ self._active_port_scans.pop(payload.uuid)
+ self._port_scan_responses[payload.uuid] = payload
+ self.sys_log.info(
+ f"{self.name}: Received port scan response from {payload.ip_address} on port {payload.port.value} "
+ f"({payload.port.name}) over {payload.protocol.name}"
+ )
+
+ def _process_port_scan_request(self, payload: PortScanPayload, session_id: str) -> None:
+ """
+ Process a port scan request.
+
+ :param payload: The port scan payload received in the request.
+ :type payload: PortScanPayload
+ :param session_id: The session ID for the port scan request.
+ :type session_id: str
+ """
+ if self.software_manager.check_port_is_open(port=payload.port, protocol=payload.protocol):
+ payload.request = False
+ self.sys_log.info(
+ f"{self.name}: Responding to port scan request for port {payload.port.value} "
+ f"({payload.port.name}) over {payload.protocol.name}",
+ True,
+ )
+ self.software_manager.send_payload_to_session_manager(payload=payload, session_id=session_id)
+
+ @validate_call()
+ def port_scan(
+ self,
+ target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]],
+ target_protocol: Optional[Union[IPProtocol, List[IPProtocol]]] = None,
+ target_port: Optional[Union[Port, List[Port]]] = None,
+ show: bool = True,
+ json_serializable: bool = False,
+ ) -> Dict[IPv4Address, Dict[IPProtocol, List[Port]]]:
+ """
+ Perform a port scan on the target IP address(es).
+
+ :param target_ip_address: The target IP address(es) or network(s) for the port scan.
+ :type target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]]
+ :param target_protocol: The protocol(s) to use for the port scan. Defaults to None, which includes TCP and UDP.
+ :type target_protocol: Optional[Union[IPProtocol, List[IPProtocol]]]
+ :param target_port: The port(s) to scan. Defaults to None, which includes all valid ports.
+ :type target_port: Optional[Union[Port, List[Port]]]
+ :param show: Flag indicating whether to display the scan results. Defaults to True.
+ :type show: bool
+ :param json_serializable: Flag indicating whether the return value should be JSON serializable. Defaults to
+ False.
+ :type json_serializable: bool
+
+ :return: A dictionary mapping IP addresses to protocols and lists of open ports.
+ :rtype: Dict[IPv4Address, Dict[IPProtocol, List[Port]]]
+ """
+ ip_addresses = self._explode_ip_address_network_array(target_ip_address)
+
+ if isinstance(target_port, Port):
+ target_port = [target_port]
+ elif target_port is None:
+ target_port = [port for port in Port if port not in {Port.NONE, Port.UNUSED}]
+
+ if isinstance(target_protocol, IPProtocol):
+ target_protocol = [target_protocol]
+ elif target_protocol is None:
+ target_protocol = [IPProtocol.TCP, IPProtocol.UDP]
+
+ scan_type = self._determine_port_scan_type(list(ip_addresses), target_port)
+ active_ports = {}
+ if show:
+ table = PrettyTable(["IP Address", "Port", "Name", "Protocol"])
+ table.align = "l"
+ table.title = f"{self.software_manager.node.hostname} NMAP Port Scan ({scan_type})"
+ self.sys_log.info(f"{self.name}: Starting port scan")
+ for ip_address in ip_addresses:
+ # Prevent port scan on this node
+ if self.software_manager.node.ip_is_network_interface(ip_address=ip_address):
+ continue
+ for protocol in target_protocol:
+ for port in set(target_port):
+ port_open = self._check_port_open_on_ip_address(ip_address=ip_address, port=port, protocol=protocol)
+
+ if port_open:
+ table.add_row([ip_address, port.value, port.name, protocol.name])
+ _ip_address = ip_address if not json_serializable else str(ip_address)
+ _protocol = protocol if not json_serializable else protocol.value
+ _port = port if not json_serializable else port.value
+ if _ip_address not in active_ports:
+ active_ports[_ip_address] = dict()
+ if _protocol not in active_ports[_ip_address]:
+ active_ports[_ip_address][_protocol] = []
+ active_ports[_ip_address][_protocol].append(_port)
+
+ if show:
+ print(table.get_string(sortby="IP Address"))
+
+ return active_ports
+
+ def network_service_recon(
+ self,
+ target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]],
+ target_protocol: Optional[Union[IPProtocol, List[IPProtocol]]] = None,
+ target_port: Optional[Union[Port, List[Port]]] = None,
+ show: bool = True,
+ show_online_only: bool = True,
+ json_serializable: bool = False,
+ ) -> Dict[IPv4Address, Dict[IPProtocol, List[Port]]]:
+ """
+ Perform a network service reconnaissance which includes a ping scan followed by a port scan.
+
+ This method combines the functionalities of a ping scan and a port scan to provide a comprehensive
+ overview of the services on the network. It first identifies active hosts in the target IP range by performing
+ a ping scan. Once the active hosts are identified, it performs a port scan on these hosts to identify open
+ ports and running services. This two-step process ensures that the port scan is performed only on live hosts,
+ optimising the scanning process and providing accurate results.
+
+ :param target_ip_address: The target IP address(es) or network(s) for the port scan.
+ :type target_ip_address: Union[IPV4Address, List[IPV4Address], IPv4Network, List[IPv4Network]]
+ :param target_protocol: The protocol(s) to use for the port scan. Defaults to None, which includes TCP and UDP.
+ :type target_protocol: Optional[Union[IPProtocol, List[IPProtocol]]]
+ :param target_port: The port(s) to scan. Defaults to None, which includes all valid ports.
+ :type target_port: Optional[Union[Port, List[Port]]]
+ :param show: Flag indicating whether to display the scan results. Defaults to True.
+ :type show: bool
+ :param show_online_only: Flag indicating whether to show only the online hosts. Defaults to True.
+ :type show_online_only: bool
+ :param json_serializable: Flag indicating whether the return value should be JSON serializable. Defaults to
+ False.
+ :type json_serializable: bool
+
+ :return: A dictionary mapping IP addresses to protocols and lists of open ports.
+ :rtype: Dict[IPv4Address, Dict[IPProtocol, List[Port]]]
+ """
+ ping_scan_results = self.ping_scan(
+ target_ip_address=target_ip_address, show=show, show_online_only=show_online_only, json_serializable=False
+ )
+ return self.port_scan(
+ target_ip_address=ping_scan_results,
+ target_protocol=target_protocol,
+ target_port=target_port,
+ show=show,
+ json_serializable=json_serializable,
+ )
+
+ def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
+ """
+ Receive and process a payload.
+
+ :param payload: The payload to be processed.
+ :type payload: Any
+ :param session_id: The session ID associated with the payload.
+ :type session_id: str
+
+ :return: True if the payload was successfully processed, False otherwise.
+ :rtype: bool
+ """
+ if isinstance(payload, PortScanPayload):
+ if payload.request:
+ self._process_port_scan_request(payload=payload, session_id=session_id)
+ else:
+ self._process_port_scan_response(payload=payload)
+
+ return True
diff --git a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py
index bac5d583..af4a59d4 100644
--- a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py
+++ b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py
@@ -1,9 +1,7 @@
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
-from enum import IntEnum
from ipaddress import IPv4Address
from typing import Dict, Optional
-from primaite.game.science import simulate_trial
from primaite.interface.request import RequestResponse
from primaite.simulator.core import RequestManager, RequestType
from primaite.simulator.network.transmission.network_layer import IPProtocol
@@ -12,43 +10,10 @@ from primaite.simulator.system.applications.application import Application
from primaite.simulator.system.applications.database_client import DatabaseClient, DatabaseClientConnection
-class RansomwareAttackStage(IntEnum):
- """
- Enumeration representing different attack stages of the ransomware script.
-
- This enumeration defines the various stages a data manipulation attack can be in during its lifecycle
- in the simulation.
- Each stage represents a specific phase in the attack process.
- """
-
- NOT_STARTED = 0
- "Indicates that the attack has not started yet."
- DOWNLOAD = 1
- "Installing the Encryption Script - Testing"
- INSTALL = 2
- "The stage where logon procedures are simulated."
- ACTIVATE = 3
- "Operating Status Changes"
- PROPAGATE = 4
- "Represents the stage of performing a horizontal port scan on the target."
- COMMAND_AND_CONTROL = 5
- "Represents the stage of setting up a rely C2 Beacon (Not Implemented)"
- PAYLOAD = 6
- "Stage of actively attacking the target."
- SUCCEEDED = 7
- "Indicates the attack has been successfully completed."
- FAILED = 8
- "Signifies that the attack has failed."
-
-
class RansomwareScript(Application):
"""Ransomware Kill Chain - Designed to be used by the TAP001 Agent on the example layout Network.
- :ivar payload: The attack stage query payload. (Default Corrupt)
- :ivar target_scan_p_of_success: The probability of success for the target scan stage.
- :ivar c2_beacon_p_of_success: The probability of success for the c2_beacon stage
- :ivar ransomware_encrypt_p_of_success: The probability of success for the ransomware 'attack' (encrypt) stage.
- :ivar repeat: Whether to repeat attacking once finished.
+ :ivar payload: The attack stage query payload. (Default ENCRYPT)
"""
server_ip_address: Optional[IPv4Address] = None
@@ -57,16 +22,6 @@ class RansomwareScript(Application):
"""Password required to access the database."""
payload: Optional[str] = "ENCRYPT"
"Payload String for the payload stage"
- target_scan_p_of_success: float = 0.9
- "Probability of the target scan succeeding: Default 0.9"
- c2_beacon_p_of_success: float = 0.9
- "Probability of the c2 beacon setup stage succeeding: Default 0.9"
- ransomware_encrypt_p_of_success: float = 0.9
- "Probability of the ransomware attack succeeding: Default 0.9"
- repeat: bool = False
- "If true, the Denial of Service bot will keep performing the attack."
- attack_stage: RansomwareAttackStage = RansomwareAttackStage.NOT_STARTED
- "The ransomware attack stage. See RansomwareAttackStage Class"
def __init__(self, **kwargs):
kwargs["name"] = "RansomwareScript"
@@ -91,7 +46,7 @@ class RansomwareScript(Application):
@property
def _host_db_client(self) -> DatabaseClient:
"""Return the database client that is installed on the same machine as the Ransomware Script."""
- db_client = self.software_manager.software.get("DatabaseClient")
+ db_client: DatabaseClient = self.software_manager.software.get("DatabaseClient")
if db_client is None:
self.sys_log.warning(f"{self.__class__.__name__} cannot find a database client on its host.")
return db_client
@@ -109,16 +64,6 @@ class RansomwareScript(Application):
)
return rm
- def _activate(self):
- """
- Simulate the install process as the initial stage of the attack.
-
- Advances the attack stage to 'ACTIVATE' attack state.
- """
- if self.attack_stage == RansomwareAttackStage.INSTALL:
- self.sys_log.info(f"{self.name}: Activated!")
- self.attack_stage = RansomwareAttackStage.ACTIVATE
-
def run(self) -> bool:
"""Calls the parent classes execute method before starting the application loop."""
super().run()
@@ -134,20 +79,9 @@ class RansomwareScript(Application):
return False
if self.server_ip_address and self.payload:
self.sys_log.info(f"{self.name}: Running")
- self.attack_stage = RansomwareAttackStage.NOT_STARTED
- self._local_download()
- self._install()
- self._activate()
- self._perform_target_scan()
- self._setup_beacon()
- self._perform_ransomware_encrypt()
-
- if self.repeat and self.attack_stage in (
- RansomwareAttackStage.SUCCEEDED,
- RansomwareAttackStage.FAILED,
- ):
- self.attack_stage = RansomwareAttackStage.NOT_STARTED
- return True
+ if self._perform_ransomware_encrypt():
+ return True
+ return False
else:
self.sys_log.warning(f"{self.name}: Failed to start as it requires both a target_ip_address and payload.")
return False
@@ -157,10 +91,6 @@ class RansomwareScript(Application):
server_ip_address: IPv4Address,
server_password: Optional[str] = None,
payload: Optional[str] = None,
- target_scan_p_of_success: Optional[float] = None,
- c2_beacon_p_of_success: Optional[float] = None,
- ransomware_encrypt_p_of_success: Optional[float] = None,
- repeat: bool = True,
):
"""
Configure the Ransomware Script to communicate with a DatabaseService.
@@ -168,10 +98,6 @@ class RansomwareScript(Application):
:param server_ip_address: The IP address of the Node the DatabaseService is on.
:param server_password: The password on the DatabaseService.
:param payload: The attack stage query (Encrypt / Delete)
- :param target_scan_p_of_success: The probability of success for the target scan stage.
- :param c2_beacon_p_of_success: The probability of success for the c2_beacon stage
- :param ransomware_encrypt_p_of_success: The probability of success for the ransomware 'attack' (encrypt) stage.
- :param repeat: Whether to repeat attacking once finished.
"""
if server_ip_address:
self.server_ip_address = server_ip_address
@@ -179,74 +105,15 @@ class RansomwareScript(Application):
self.server_password = server_password
if payload:
self.payload = payload
- if target_scan_p_of_success:
- self.target_scan_p_of_success = target_scan_p_of_success
- if c2_beacon_p_of_success:
- self.c2_beacon_p_of_success = c2_beacon_p_of_success
- if ransomware_encrypt_p_of_success:
- self.ransomware_encrypt_p_of_success = ransomware_encrypt_p_of_success
- if repeat:
- self.repeat = repeat
self.sys_log.info(
- f"{self.name}: Configured the {self.name} with {server_ip_address=}, {payload=}, {server_password=}, "
- f"{repeat=}."
+ f"{self.name}: Configured the {self.name} with {server_ip_address=}, {payload=}, {server_password=}."
)
- def _install(self):
- """
- Simulate the install stage in the kill-chain.
-
- Advances the attack stage to 'ACTIVATE' if successful.
-
- From this attack stage onwards.
- the ransomware application is now visible from this point onwardin the observation space.
- """
- if self.attack_stage == RansomwareAttackStage.DOWNLOAD:
- self.sys_log.info(f"{self.name}: Malware installed on the local file system")
- downloads_folder = self.file_system.get_folder(folder_name="downloads")
- ransomware_file = downloads_folder.get_file(file_name="ransom_script.pdf")
- ransomware_file.num_access += 1
- self.attack_stage = RansomwareAttackStage.INSTALL
-
- def _setup_beacon(self):
- """
- Simulates setting up a c2 beacon; currently a pseudo step for increasing red variance.
-
- Advances the attack stage to 'COMMAND AND CONTROL` if successful.
-
- :param p_of_sucess: Probability of a successful c2 setup (Advancing this step),
- by default the success rate is 0.5
- """
- if self.attack_stage == RansomwareAttackStage.PROPAGATE:
- self.sys_log.info(f"{self.name} Attempting to set up C&C Beacon - Scan 1/2")
- if simulate_trial(self.c2_beacon_p_of_success):
- self.sys_log.info(f"{self.name} C&C Successful setup - Scan 2/2")
- c2c_setup = True # TODO Implement the c2c step via an FTP Application/Service
- if c2c_setup:
- self.attack_stage = RansomwareAttackStage.COMMAND_AND_CONTROL
-
- def _perform_target_scan(self):
- """
- Perform a simulated port scan to check for open SQL ports.
-
- Advances the attack stage to `PROPAGATE` if successful.
-
- :param p_of_success: Probability of successful port scan, by default 0.1.
- """
- if self.attack_stage == RansomwareAttackStage.ACTIVATE:
- # perform a port scan to identify that the SQL port is open on the server
- self.sys_log.info(f"{self.name}: Scanning for vulnerable databases - Scan 0/2")
- if simulate_trial(self.target_scan_p_of_success):
- self.sys_log.info(f"{self.name}: Found a target database! Scan 1/2")
- port_is_open = True # TODO Implement a NNME Triggering scan as a seperate Red Application
- if port_is_open:
- self.attack_stage = RansomwareAttackStage.PROPAGATE
-
def attack(self) -> bool:
"""Perform the attack steps after opening the application."""
+ self.run()
if not self._can_perform_action():
self.sys_log.warning("Ransomware application is unable to perform it's actions.")
- self.run()
self.num_executions += 1
return self._application_loop()
@@ -255,57 +122,30 @@ class RansomwareScript(Application):
self._db_connection = self._host_db_client.get_new_connection()
return True if self._db_connection else False
- def _perform_ransomware_encrypt(self):
+ def _perform_ransomware_encrypt(self) -> bool:
"""
Execute the Ransomware Encrypt payload on the target.
Advances the attack stage to `COMPLETE` if successful, or 'FAILED' if unsuccessful.
- :param p_of_success: Probability of successfully performing ransomware encryption, by default 0.1.
"""
if self._host_db_client is None:
self.sys_log.info(f"{self.name}: Failed to connect to db_client - Ransomware Script")
- self.attack_stage = RansomwareAttackStage.FAILED
- return
+ return False
self._host_db_client.server_ip_address = self.server_ip_address
self._host_db_client.server_password = self.server_password
- if self.attack_stage == RansomwareAttackStage.COMMAND_AND_CONTROL:
- if simulate_trial(self.ransomware_encrypt_p_of_success):
- self.sys_log.info(f"{self.name}: Attempting to launch payload")
- if not self._db_connection:
- self._establish_db_connection()
- if self._db_connection:
- attack_successful = self._db_connection.query(self.payload)
- self.sys_log.info(f"{self.name} Payload delivered: {self.payload}")
- if attack_successful:
- self.sys_log.info(f"{self.name}: Payload Successful")
- self.attack_stage = RansomwareAttackStage.SUCCEEDED
- else:
- self.sys_log.info(f"{self.name}: Payload failed")
- self.attack_stage = RansomwareAttackStage.FAILED
+ self.sys_log.info(f"{self.name}: Attempting to launch payload")
+ if not self._db_connection:
+ self._establish_db_connection()
+ if self._db_connection:
+ attack_successful = self._db_connection.query(self.payload)
+ self.sys_log.info(f"{self.name} Payload delivered: {self.payload}")
+ if attack_successful:
+ self.sys_log.info(f"{self.name}: Payload Successful")
+ return True
+ else:
+ self.sys_log.info(f"{self.name}: Payload failed")
+ return False
else:
self.sys_log.warning("Attack Attempted to launch too quickly")
- self.attack_stage = RansomwareAttackStage.FAILED
-
- def _local_download(self):
- """Downloads itself via the onto the local file_system."""
- if self.attack_stage == RansomwareAttackStage.NOT_STARTED:
- if self._local_download_verify():
- self.attack_stage = RansomwareAttackStage.DOWNLOAD
- else:
- self.sys_log.info("Malware failed to create a installation location")
- self.attack_stage = RansomwareAttackStage.FAILED
- else:
- self.sys_log.info("Malware failed to download")
- self.attack_stage = RansomwareAttackStage.FAILED
-
- def _local_download_verify(self) -> bool:
- """Verifies a download location - Creates one if needed."""
- for folder in self.file_system.folders:
- if self.file_system.folders[folder].name == "downloads":
- self.file_system.num_file_creations += 1
- return True
-
- self.file_system.create_folder("downloads")
- self.file_system.create_file(folder_name="downloads", file_name="ransom_script.pdf")
- return True
+ return False
diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py
index 31ad7d42..e2266c2d 100644
--- a/src/primaite/simulator/system/core/software_manager.py
+++ b/src/primaite/simulator/system/core/software_manager.py
@@ -79,6 +79,31 @@ class SoftwareManager:
open_ports.append(software.port)
return open_ports
+ def check_port_is_open(self, port: Port, protocol: IPProtocol) -> bool:
+ """
+ Check if a specific port is open and running a service using the specified protocol.
+
+ This method iterates through all installed software on the node and checks if any of them
+ are using the specified port and protocol and are currently in a running state. It returns True if any software
+ is found running on the specified port and protocol, otherwise False.
+
+
+ :param port: The port to check.
+ :type port: Port
+ :param protocol: The protocol to check (e.g., TCP, UDP).
+ :type protocol: IPProtocol
+ :return: True if the port is open and a service is running on it using the specified protocol, False otherwise.
+ :rtype: bool
+ """
+ for software in self.software.values():
+ if (
+ software.port == port
+ and software.protocol == protocol
+ and software.operating_state in {ApplicationOperatingState.RUNNING, ServiceOperatingState.RUNNING}
+ ):
+ return True
+ return False
+
def install(self, software_class: Type[IOSoftwareClass]):
"""
Install an Application or Service.
@@ -151,6 +176,7 @@ class SoftwareManager:
self,
payload: Any,
dest_ip_address: Optional[Union[IPv4Address, IPv4Network]] = None,
+ src_port: Optional[Port] = None,
dest_port: Optional[Port] = None,
ip_protocol: IPProtocol = IPProtocol.TCP,
session_id: Optional[str] = None,
@@ -171,6 +197,7 @@ class SoftwareManager:
return self.session_manager.receive_payload_from_software_manager(
payload=payload,
dst_ip_address=dest_ip_address,
+ src_port=src_port,
dst_port=dest_port,
ip_protocol=ip_protocol,
session_id=session_id,
@@ -191,6 +218,9 @@ class SoftwareManager:
:param payload: The payload being received.
:param session: The transport session the payload originates from.
"""
+ if payload.__class__.__name__ == "PortScanPayload":
+ self.software.get("NMAP").receive(payload=payload, session_id=session_id)
+ return
receiver: Optional[Union[Service, Application]] = self.port_protocol_mapping.get((port, protocol), None)
if receiver:
receiver.receive(
diff --git a/src/primaite/utils/converters.py b/src/primaite/utils/converters.py
new file mode 100644
index 00000000..f803851d
--- /dev/null
+++ b/src/primaite/utils/converters.py
@@ -0,0 +1,26 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+from enum import Enum
+from typing import Any, Dict
+
+
+def convert_dict_enum_keys_to_enum_values(d: Dict[Any, Any]) -> Dict[Any, Any]:
+ """
+ Convert dictionary keys from enums to their corresponding values.
+
+ :param d: dict
+ The dictionary with enum keys to be converted.
+ :return: dict
+ The dictionary with enum values as keys.
+ """
+ result = {}
+ for key, value in d.items():
+ if isinstance(key, Enum):
+ new_key = key.value
+ else:
+ new_key = key
+
+ if isinstance(value, dict):
+ result[new_key] = convert_dict_enum_keys_to_enum_values(value)
+ else:
+ result[new_key] = value
+ return result
diff --git a/src/primaite/utils/session_output_reader.py b/src/primaite/utils/session_output_reader.py
index ae582bd0..b9ad68a1 100644
--- a/src/primaite/utils/session_output_reader.py
+++ b/src/primaite/utils/session_output_reader.py
@@ -3,7 +3,7 @@
raise DeprecationWarning(
"Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet."
)
-# © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
from pathlib import Path
from typing import Any, Dict, Tuple, Union
@@ -12,16 +12,16 @@ from typing import Any, Dict, Tuple, Union
import polars as pl
-def av_rewards_dict(av_rewards_csv_file: Union[str, Path]) -> Dict[int, float]:
+def total_rewards_dict(total_rewards_csv_file: Union[str, Path]) -> Dict[int, float]:
"""
Read an average rewards per episode csv file and return as a dict.
The dictionary keys are the episode number, and the values are the mean reward that episode.
- :param av_rewards_csv_file: The average rewards per episode csv file path.
+ :param total_rewards_csv_file: The average rewards per episode csv file path.
:return: The average rewards per episode csv as a dict.
"""
- df_dict = pl.read_csv(av_rewards_csv_file).to_dict()
+ df_dict = pl.read_csv(total_rewards_csv_file).to_dict()
return {int(v): df_dict["Average Reward"][i] for i, v in enumerate(df_dict["Episode"])}
diff --git a/src/primaite/utils/session_output_writer.py b/src/primaite/utils/session_output_writer.py
index d272438f..75a97f60 100644
--- a/src/primaite/utils/session_output_writer.py
+++ b/src/primaite/utils/session_output_writer.py
@@ -3,7 +3,7 @@
raise DeprecationWarning(
"Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet."
)
-# © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
import csv
from logging import Logger
from typing import Final, List, Tuple, TYPE_CHECKING, Union
@@ -27,9 +27,9 @@ class SessionOutputWriter:
Is used to write session outputs to csv file.
"""
- _AV_REWARD_PER_EPISODE_HEADER: Final[List[str]] = [
+ _TOTAL_REWARD_PER_EPISODE_HEADER: Final[List[str]] = [
"Episode",
- "Average Reward",
+ "Total Reward",
]
def __init__(
@@ -44,7 +44,7 @@ class SessionOutputWriter:
:param env: PrimAITE gym environment.
:type env: Primaite
:param transaction_writer: If `true`, this will output a full account of every transaction taken by the agent.
- If `false` it will output the average reward per episode, defaults to False
+ If `false` it will output the total reward per episode, defaults to False
:type transaction_writer: bool, optional
:param learning_session: Set to `true` to indicate that the current session is a training session. This
determines the name of the folder which contains the final output csv. Defaults to True
@@ -57,7 +57,7 @@ class SessionOutputWriter:
if self.transaction_writer:
fn = f"all_transactions_{self._env.timestamp_str}.csv"
else:
- fn = f"average_reward_per_episode_{self._env.timestamp_str}.csv"
+ fn = f"total_reward_per_episode_{self._env.timestamp_str}.csv"
self._csv_file_path: "Path"
if self.learning_session:
@@ -95,7 +95,7 @@ class SessionOutputWriter:
if isinstance(data, Transaction):
header, data = data.as_csv_data()
else:
- header = self._AV_REWARD_PER_EPISODE_HEADER
+ header = self._TOTAL_REWARD_PER_EPISODE_HEADER
if self._first_write:
self._init_csv_writer()
diff --git a/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml b/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml
new file mode 100644
index 00000000..c5508f13
--- /dev/null
+++ b/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml
@@ -0,0 +1,137 @@
+io_settings:
+ save_step_metadata: false
+ save_pcap_logs: true
+ save_sys_logs: true
+ sys_log_level: WARNING
+
+
+
+game:
+ max_episode_length: 256
+ ports:
+ - ARP
+ - DNS
+ - HTTP
+ - POSTGRES_SERVER
+ protocols:
+ - ICMP
+ - TCP
+ - UDP
+
+agents:
+ - ref: client_1_red_nmap
+ team: RED
+ type: ProbabilisticAgent
+ observation_space: null
+ action_space:
+ options:
+ nodes:
+ - node_name: client_1
+ applications:
+ - application_name: NMAP
+ max_folders_per_node: 1
+ max_files_per_folder: 1
+ max_services_per_node: 1
+ max_applications_per_node: 1
+ action_list:
+ - type: NODE_NMAP_NETWORK_SERVICE_RECON
+ action_map:
+ 0:
+ action: NODE_NMAP_NETWORK_SERVICE_RECON
+ options:
+ source_node: client_1
+ target_ip_address: 192.168.10.0/24
+ target_port: 80
+ target_protocol: tcp
+
+ reward_function:
+ reward_components:
+ - type: DUMMY
+
+ agent_settings:
+ action_probabilities:
+ 0: 1.0
+
+
+
+simulation:
+ network:
+ nodes:
+ - hostname: switch_1
+ num_ports: 8
+ type: switch
+
+ - hostname: switch_2
+ num_ports: 8
+ type: switch
+
+ - hostname: router_1
+ type: router
+ ports:
+ 1:
+ ip_address: 192.168.1.1
+ subnet_mask: 255.255.255.0
+ 2:
+ ip_address: 192.168.10.1
+ subnet_mask: 255.255.255.0
+ acl:
+ 1:
+ action: PERMIT
+
+ - hostname: client_1
+ type: computer
+ ip_address: 192.168.10.21
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: client_2
+ type: computer
+ ip_address: 192.168.10.22
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: server_1
+ type: server
+ ip_address: 192.168.1.10
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+ - hostname: server_2
+ type: server
+ ip_address: 192.168.1.14
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+
+
+
+ links:
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 2
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: client_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: client_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 2
+
+ - endpoint_a_hostname: server_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: server_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 2
diff --git a/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml b/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml
new file mode 100644
index 00000000..33ba3d19
--- /dev/null
+++ b/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml
@@ -0,0 +1,135 @@
+io_settings:
+ save_step_metadata: false
+ save_pcap_logs: true
+ save_sys_logs: true
+ sys_log_level: WARNING
+
+
+
+game:
+ max_episode_length: 256
+ ports:
+ - ARP
+ - DNS
+ - HTTP
+ - POSTGRES_SERVER
+ protocols:
+ - ICMP
+ - TCP
+ - UDP
+
+agents:
+ - ref: client_1_red_nmap
+ team: RED
+ type: ProbabilisticAgent
+ observation_space: null
+ action_space:
+ options:
+ nodes:
+ - node_name: client_1
+ applications:
+ - application_name: NMAP
+ max_folders_per_node: 1
+ max_files_per_folder: 1
+ max_services_per_node: 1
+ max_applications_per_node: 1
+ action_list:
+ - type: NODE_NMAP_PING_SCAN
+ action_map:
+ 0:
+ action: NODE_NMAP_PING_SCAN
+ options:
+ source_node: client_1
+ target_ip_address: 192.168.1.0/24
+
+ reward_function:
+ reward_components:
+ - type: DUMMY
+
+ agent_settings:
+ action_probabilities:
+ 0: 1.0
+
+
+
+simulation:
+ network:
+ nodes:
+ - hostname: switch_1
+ num_ports: 8
+ type: switch
+
+ - hostname: switch_2
+ num_ports: 8
+ type: switch
+
+ - hostname: router_1
+ type: router
+ ports:
+ 1:
+ ip_address: 192.168.1.1
+ subnet_mask: 255.255.255.0
+ 2:
+ ip_address: 192.168.10.1
+ subnet_mask: 255.255.255.0
+ acl:
+ 1:
+ action: PERMIT
+
+ - hostname: client_1
+ type: computer
+ ip_address: 192.168.10.21
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: client_2
+ type: computer
+ ip_address: 192.168.10.22
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: server_1
+ type: server
+ ip_address: 192.168.1.10
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+ - hostname: server_2
+ type: server
+ ip_address: 192.168.1.14
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+
+
+
+ links:
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 2
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: client_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: client_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 2
+
+ - endpoint_a_hostname: server_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: server_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 2
diff --git a/tests/assets/configs/nmap_port_scan_red_agent_config.yaml b/tests/assets/configs/nmap_port_scan_red_agent_config.yaml
new file mode 100644
index 00000000..08944ee5
--- /dev/null
+++ b/tests/assets/configs/nmap_port_scan_red_agent_config.yaml
@@ -0,0 +1,135 @@
+io_settings:
+ save_step_metadata: false
+ save_pcap_logs: true
+ save_sys_logs: true
+ sys_log_level: WARNING
+
+
+
+game:
+ max_episode_length: 256
+ ports:
+ - ARP
+ - DNS
+ - HTTP
+ - POSTGRES_SERVER
+ protocols:
+ - ICMP
+ - TCP
+ - UDP
+
+agents:
+ - ref: client_1_red_nmap
+ team: RED
+ type: ProbabilisticAgent
+ observation_space: null
+ action_space:
+ options:
+ nodes:
+ - node_name: client_1
+ applications:
+ - application_name: NMAP
+ max_folders_per_node: 1
+ max_files_per_folder: 1
+ max_services_per_node: 1
+ max_applications_per_node: 1
+ action_list:
+ - type: NODE_NMAP_PORT_SCAN
+ action_map:
+ 0:
+ action: NODE_NMAP_PORT_SCAN
+ options:
+ source_node: client_1
+ target_ip_address: 192.168.10.0/24
+
+ reward_function:
+ reward_components:
+ - type: DUMMY
+
+ agent_settings:
+ action_probabilities:
+ 0: 1.0
+
+
+
+simulation:
+ network:
+ nodes:
+ - hostname: switch_1
+ num_ports: 8
+ type: switch
+
+ - hostname: switch_2
+ num_ports: 8
+ type: switch
+
+ - hostname: router_1
+ type: router
+ ports:
+ 1:
+ ip_address: 192.168.1.1
+ subnet_mask: 255.255.255.0
+ 2:
+ ip_address: 192.168.10.1
+ subnet_mask: 255.255.255.0
+ acl:
+ 1:
+ action: PERMIT
+
+ - hostname: client_1
+ type: computer
+ ip_address: 192.168.10.21
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: client_2
+ type: computer
+ ip_address: 192.168.10.22
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.10.1
+
+ - hostname: server_1
+ type: server
+ ip_address: 192.168.1.10
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+ - hostname: server_2
+ type: server
+ ip_address: 192.168.1.14
+ subnet_mask: 255.255.255.0
+ default_gateway: 192.168.1.1
+
+
+
+
+ links:
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: router_1
+ endpoint_a_port: 2
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 8
+
+ - endpoint_a_hostname: client_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: client_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_2
+ endpoint_b_port: 2
+
+ - endpoint_a_hostname: server_1
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 1
+
+ - endpoint_a_hostname: server_2
+ endpoint_a_port: 1
+ endpoint_b_hostname: switch_1
+ endpoint_b_port: 2
diff --git a/tests/integration_tests/system/red_applications/test_ransomware_script.py b/tests/integration_tests/system/red_applications/test_ransomware_script.py
index 50332b7c..2e3a0b1c 100644
--- a/tests/integration_tests/system/red_applications/test_ransomware_script.py
+++ b/tests/integration_tests/system/red_applications/test_ransomware_script.py
@@ -10,12 +10,8 @@ from primaite.simulator.network.hardware.nodes.host.computer import Computer
from primaite.simulator.network.hardware.nodes.host.server import Server
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
from primaite.simulator.network.transmission.transport_layer import Port
-from primaite.simulator.system.applications.application import ApplicationOperatingState
from primaite.simulator.system.applications.database_client import DatabaseClient, DatabaseClientConnection
-from primaite.simulator.system.applications.red_applications.ransomware_script import (
- RansomwareAttackStage,
- RansomwareScript,
-)
+from primaite.simulator.system.applications.red_applications.ransomware_script import RansomwareScript
from primaite.simulator.system.services.database.database_service import DatabaseService
from primaite.simulator.system.software import SoftwareHealthState
@@ -86,54 +82,24 @@ def ransomware_script_db_server_green_client(example_network) -> Network:
return network
-def test_repeating_ransomware_script_attack(ransomware_script_and_db_server):
+def test_ransomware_script_attack(ransomware_script_and_db_server):
"""Test a repeating data manipulation attack."""
RansomwareScript, computer, db_server_service, server = ransomware_script_and_db_server
+ computer.apply_timestep(timestep=0)
+ server.apply_timestep(timestep=0)
assert db_server_service.health_state_actual is SoftwareHealthState.GOOD
- assert computer.file_system.num_file_creations == 0
+ assert server.file_system.num_file_creations == 1
- RansomwareScript.target_scan_p_of_success = 1
- RansomwareScript.c2_beacon_p_of_success = 1
- RansomwareScript.ransomware_encrypt_p_of_success = 1
- RansomwareScript.repeat = True
RansomwareScript.attack()
- assert RansomwareScript.attack_stage == RansomwareAttackStage.NOT_STARTED
- assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.COMPROMISED
- assert computer.file_system.num_file_creations == 1
+ assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.CORRUPT
+ assert server.file_system.num_file_creations == 2
computer.apply_timestep(timestep=1)
server.apply_timestep(timestep=1)
- assert RansomwareScript.attack_stage == RansomwareAttackStage.NOT_STARTED
- assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.COMPROMISED
-
-
-def test_repeating_ransomware_script_attack(ransomware_script_and_db_server):
- """Test a repeating ransowmare script attack."""
- RansomwareScript, computer, db_server_service, server = ransomware_script_and_db_server
-
- assert db_server_service.health_state_actual is SoftwareHealthState.GOOD
-
- RansomwareScript.target_scan_p_of_success = 1
- RansomwareScript.c2_beacon_p_of_success = 1
- RansomwareScript.ransomware_encrypt_p_of_success = 1
- RansomwareScript.repeat = False
- RansomwareScript.attack()
-
- assert RansomwareScript.attack_stage == RansomwareAttackStage.SUCCEEDED
assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.CORRUPT
- assert computer.file_system.num_file_creations == 1
-
- computer.apply_timestep(timestep=1)
- computer.pre_timestep(timestep=1)
- server.apply_timestep(timestep=1)
- server.pre_timestep(timestep=1)
-
- assert RansomwareScript.attack_stage == RansomwareAttackStage.SUCCEEDED
- assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.CORRUPT
- assert computer.file_system.num_file_creations == 0
def test_ransomware_disrupts_green_agent_connection(ransomware_script_db_server_green_client):
@@ -154,10 +120,6 @@ def test_ransomware_disrupts_green_agent_connection(ransomware_script_db_server_
assert green_db_client_connection.query("SELECT")
assert green_db_client.last_query_response.get("status_code") == 200
- ransomware_script_application.target_scan_p_of_success = 1
- ransomware_script_application.ransomware_encrypt_p_of_success = 1
- ransomware_script_application.c2_beacon_p_of_success = 1
- ransomware_script_application.repeat = False
ransomware_script_application.attack()
assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.CORRUPT
diff --git a/tests/integration_tests/system/test_nmap.py b/tests/integration_tests/system/test_nmap.py
new file mode 100644
index 00000000..bbfa4f43
--- /dev/null
+++ b/tests/integration_tests/system/test_nmap.py
@@ -0,0 +1,186 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+from enum import Enum
+from ipaddress import IPv4Address, IPv4Network
+
+import yaml
+
+from primaite.game.game import PrimaiteGame
+from primaite.simulator.network.transmission.network_layer import IPProtocol
+from primaite.simulator.network.transmission.transport_layer import Port
+from primaite.simulator.system.applications.nmap import NMAP
+from tests import TEST_ASSETS_ROOT
+
+
+def test_ping_scan_all_on(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ expected_result = [IPv4Address("192.168.1.10"), IPv4Address("192.168.1.14")]
+ actual_result = client_1_nmap.ping_scan(target_ip_address=["192.168.1.10", "192.168.1.14"])
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_ping_scan_all_on_full_network(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ expected_result = [IPv4Address("192.168.1.1"), IPv4Address("192.168.1.10"), IPv4Address("192.168.1.14")]
+ actual_result = client_1_nmap.ping_scan(target_ip_address=IPv4Network("192.168.1.0/24"))
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_ping_scan_some_on(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ network.get_node_by_hostname("server_2").power_off()
+
+ expected_result = [IPv4Address("192.168.1.1"), IPv4Address("192.168.1.10")]
+ actual_result = client_1_nmap.ping_scan(target_ip_address=IPv4Network("192.168.1.0/24"))
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_ping_scan_all_off(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ network.get_node_by_hostname("server_1").power_off()
+ network.get_node_by_hostname("server_2").power_off()
+
+ expected_result = []
+ actual_result = client_1_nmap.ping_scan(target_ip_address=["192.168.1.10", "192.168.1.14"])
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_port_scan_one_node_one_port(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ client_2 = network.get_node_by_hostname("client_2")
+
+ actual_result = client_1_nmap.port_scan(
+ target_ip_address=client_2.network_interface[1].ip_address, target_port=Port.DNS, target_protocol=IPProtocol.TCP
+ )
+
+ expected_result = {IPv4Address("192.168.10.22"): {IPProtocol.TCP: [Port.DNS]}}
+
+ assert actual_result == expected_result
+
+
+def sort_dict(d):
+ """Recursively sorts a dictionary."""
+ if isinstance(d, dict):
+ return {k: sort_dict(v) for k, v in sorted(d.items(), key=lambda item: str(item[0]))}
+ elif isinstance(d, list):
+ return sorted(d, key=lambda item: str(item) if isinstance(item, Enum) else item)
+ elif isinstance(d, Enum):
+ return str(d)
+ else:
+ return d
+
+
+def test_port_scan_full_subnet_all_ports_and_protocols(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ actual_result = client_1_nmap.port_scan(
+ target_ip_address=IPv4Network("192.168.10.0/24"),
+ )
+
+ expected_result = {
+ IPv4Address("192.168.10.1"): {IPProtocol.UDP: [Port.ARP]},
+ IPv4Address("192.168.10.22"): {
+ IPProtocol.TCP: [Port.HTTP, Port.FTP, Port.DNS],
+ IPProtocol.UDP: [Port.ARP, Port.NTP],
+ },
+ }
+
+ assert sort_dict(actual_result) == sort_dict(expected_result)
+
+
+def test_network_service_recon_all_ports_and_protocols(example_network):
+ network = example_network
+
+ client_1 = network.get_node_by_hostname("client_1")
+ client_1_nmap: NMAP = client_1.software_manager.software["NMAP"] # noqa
+
+ actual_result = client_1_nmap.network_service_recon(
+ target_ip_address=IPv4Network("192.168.10.0/24"), target_port=Port.HTTP, target_protocol=IPProtocol.TCP
+ )
+
+ expected_result = {IPv4Address("192.168.10.22"): {IPProtocol.TCP: [Port.HTTP]}}
+
+ assert sort_dict(actual_result) == sort_dict(expected_result)
+
+
+def test_ping_scan_red_agent():
+ with open(TEST_ASSETS_ROOT / "configs/nmap_ping_scan_red_agent_config.yaml", "r") as file:
+ cfg = yaml.safe_load(file)
+
+ game = PrimaiteGame.from_config(cfg)
+
+ game.step()
+
+ expected_result = ["192.168.1.1", "192.168.1.10", "192.168.1.14"]
+
+ action_history = game.agents["client_1_red_nmap"].history
+ assert len(action_history) == 1
+ actual_result = action_history[0].response.data["live_hosts"]
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_port_scan_red_agent():
+ with open(TEST_ASSETS_ROOT / "configs/nmap_port_scan_red_agent_config.yaml", "r") as file:
+ cfg = yaml.safe_load(file)
+
+ game = PrimaiteGame.from_config(cfg)
+
+ game.step()
+
+ expected_result = {
+ "192.168.10.1": {"udp": [219]},
+ "192.168.10.22": {
+ "tcp": [80, 21, 53],
+ "udp": [219, 123],
+ },
+ }
+
+ action_history = game.agents["client_1_red_nmap"].history
+ assert len(action_history) == 1
+ actual_result = action_history[0].response.data
+
+ assert sorted(actual_result) == sorted(expected_result)
+
+
+def test_network_service_recon_red_agent():
+ with open(TEST_ASSETS_ROOT / "configs/nmap_network_service_recon_red_agent_config.yaml", "r") as file:
+ cfg = yaml.safe_load(file)
+
+ game = PrimaiteGame.from_config(cfg)
+
+ game.step()
+
+ expected_result = {"192.168.10.22": {"tcp": [80]}}
+
+ action_history = game.agents["client_1_red_nmap"].history
+ assert len(action_history) == 1
+ actual_result = action_history[0].response.data
+
+ assert sorted(actual_result) == sorted(expected_result)
diff --git a/tests/unit_tests/_primaite/_utils/__init__.py b/tests/unit_tests/_primaite/_utils/__init__.py
new file mode 100644
index 00000000..be6c00e7
--- /dev/null
+++ b/tests/unit_tests/_primaite/_utils/__init__.py
@@ -0,0 +1 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
diff --git a/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py
new file mode 100644
index 00000000..a8fb0a3a
--- /dev/null
+++ b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py
@@ -0,0 +1,84 @@
+# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
+from primaite.simulator.network.transmission.network_layer import IPProtocol
+from primaite.simulator.network.transmission.transport_layer import Port
+from primaite.utils.converters import convert_dict_enum_keys_to_enum_values
+
+
+def test_simple_conversion():
+ """
+ Test conversion of a simple dictionary with enum keys to enum values.
+
+ The original dictionary contains one level of nested dictionary with enums as keys.
+ The expected output should have string values of enums as keys.
+ """
+ original_dict = {IPProtocol.UDP: {Port.ARP: {"inbound": 0, "outbound": 1016.0}}}
+ expected_dict = {"udp": {219: {"inbound": 0, "outbound": 1016.0}}}
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict
+
+
+def test_no_enums():
+ """
+ Test conversion of a dictionary with no enum keys.
+
+ The original dictionary contains only string keys.
+ The expected output should be identical to the original dictionary.
+ """
+ original_dict = {"protocol": {"port": {"inbound": 0, "outbound": 1016.0}}}
+ expected_dict = {"protocol": {"port": {"inbound": 0, "outbound": 1016.0}}}
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict
+
+
+def test_mixed_keys():
+ """
+ Test conversion of a dictionary with a mix of enum and string keys.
+
+ The original dictionary contains both enums and strings as keys.
+ The expected output should have string values of enums and original string keys.
+ """
+ original_dict = {
+ IPProtocol.TCP: {"port": {"inbound": 0, "outbound": 1016.0}},
+ "protocol": {Port.HTTP: {"inbound": 10, "outbound": 2020.0}},
+ }
+ expected_dict = {
+ "tcp": {"port": {"inbound": 0, "outbound": 1016.0}},
+ "protocol": {80: {"inbound": 10, "outbound": 2020.0}},
+ }
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict
+
+
+def test_empty_dict():
+ """
+ Test conversion of an empty dictionary.
+
+ The original dictionary is empty.
+ The expected output should also be an empty dictionary.
+ """
+ original_dict = {}
+ expected_dict = {}
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict
+
+
+def test_nested_dicts():
+ """
+ Test conversion of a nested dictionary with multiple levels of nested dictionaries and enums as keys.
+
+ The original dictionary contains nested dictionaries with enums as keys at different levels.
+ The expected output should have string values of enums as keys at all levels.
+ """
+ original_dict = {
+ IPProtocol.UDP: {Port.ARP: {"inbound": 0, "outbound": 1016.0, "details": {IPProtocol.TCP: {"latency": "low"}}}}
+ }
+ expected_dict = {"udp": {219: {"inbound": 0, "outbound": 1016.0, "details": {"tcp": {"latency": "low"}}}}}
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict
+
+
+def test_non_dict_values():
+ """
+ Test conversion of a dictionary where some values are not dictionaries.
+
+ The original dictionary contains lists and tuples as values.
+ The expected output should preserve these non-dictionary values while converting enum keys to string values.
+ """
+ original_dict = {IPProtocol.UDP: [Port.ARP, Port.HTTP], "protocols": (IPProtocol.TCP, IPProtocol.UDP)}
+ expected_dict = {"udp": [Port.ARP, Port.HTTP], "protocols": (IPProtocol.TCP, IPProtocol.UDP)}
+ assert convert_dict_enum_keys_to_enum_values(original_dict) == expected_dict